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:
2026-01-20 09:14:05 +01:00
parent 5de087e8e0
commit 1f91d7508e
101 changed files with 13103 additions and 966 deletions

View File

@@ -0,0 +1,247 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Browser</title>
<style>
.browser-toolbar {
display: flex;
align-items: center;
padding: 8px;
background-color: #1E1E1E;
gap: 8px;
}
.browser-nav-btn {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 24px;
}
.browser-nav-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.browser-nav-btn img {
width: 28px;
height: 28px;
}
.browser-nav-btn.disabled img {
opacity: 0.3;
}
.browser-url-bar {
flex: 1;
display: flex;
align-items: center;
padding: 10px 16px;
background-color: #2D2D2D;
border-radius: 20px;
}
.browser-secure-icon {
font-size: 16px;
color: #4CAF50;
margin-right: 8px;
}
.browser-url {
flex: 1;
font-size: 16px;
color: #FFFFFF;
background: transparent;
border: none;
}
.browser-content {
flex: 1;
background-color: #FFFFFF;
overflow: auto;
}
.browser-page {
padding: 16px;
color: #000000;
}
.browser-page-title {
font-size: 24px;
font-weight: bold;
color: #1a0dab;
margin-bottom: 16px;
}
.browser-page-text {
font-size: 16px;
line-height: 1.6;
color: #333333;
margin-bottom: 16px;
}
.browser-page-link {
color: #1a0dab;
cursor: pointer;
}
.browser-page-link:hover {
text-decoration: underline;
}
.browser-search-item {
margin-bottom: 20px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
}
.browser-search-item:hover {
background-color: #f0f0f0;
}
.browser-search-title {
font-size: 18px;
color: #1a0dab;
margin-bottom: 4px;
}
.browser-search-url {
font-size: 14px;
color: #006621;
margin-bottom: 4px;
}
.browser-search-desc {
font-size: 16px;
color: #545454;
line-height: 1.4;
}
.browser-bottom-bar {
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #1E1E1E;
border-top: 1px solid #333333;
}
.browser-tab-btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
cursor: pointer;
color: #B3B3B3;
}
.browser-tab-btn:hover {
color: #FFFFFF;
}
.browser-tab-btn img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.browser-tab-btn span {
font-size: 14px;
}
.browser-tabs-indicator {
padding: 6px 10px;
border: 1px solid #B3B3B3;
border-radius: 6px;
font-size: 14px;
color: #B3B3B3;
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="browser">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- Browser Toolbar -->
<div class="browser-toolbar">
<div class="browser-nav-btn" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<div class="browser-nav-btn disabled">
<img src="../../icons/forward.tga"/>
</div>
<div class="browser-url-bar">
<span class="browser-secure-icon">S</span>
<input class="browser-url" type="text" value="example.com"/>
</div>
<div class="browser-nav-btn">
<img src="../../icons/refresh.tga"/>
</div>
<div class="browser-nav-btn">
<img src="../../icons/more.tga"/>
</div>
</div>
<!-- Browser Content -->
<div class="browser-content">
<div class="browser-page">
<div class="browser-page-title">Example Domain</div>
<div class="browser-page-text">
This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
</div>
<div class="browser-page-text">
<span class="browser-page-link">More information...</span>
</div>
<div style="margin-top: 32px; padding-top: 16px; border-top: 1px solid #e0e0e0;">
<div class="browser-page-title" style="font-size: 18px;">Related Links</div>
<div class="browser-search-item">
<div class="browser-search-title">IANA - IANA-managed Reserved Domains</div>
<div class="browser-search-url">www.iana.org > domains > reserved</div>
<div class="browser-search-desc">Certain domains are set aside and unavailable for registration.</div>
</div>
<div class="browser-search-item">
<div class="browser-search-title">RFC 2606 - Reserved Top Level DNS Names</div>
<div class="browser-search-url">tools.ietf.org > html > rfc2606</div>
<div class="browser-search-desc">This document describes domain names reserved for documentation.</div>
</div>
</div>
</div>
</div>
<!-- Bottom Bar -->
<div class="browser-bottom-bar">
<div class="browser-tab-btn" onclick="goHome()">
<img src="../../icons/home.tga"/>
<span>Home</span>
</div>
<div class="browser-tab-btn">
<span class="browser-tabs-indicator">1</span>
<span>Tabs</span>
</div>
<div class="browser-tab-btn">
<img src="../../icons/add.tga"/>
<span>New Tab</span>
</div>
<div class="browser-tab-btn">
<img src="../../icons/menu.tga"/>
<span>Menu</span>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,18 @@
{
"id": "com.mosis.browser",
"name": "Browser",
"version": "1.0.0",
"version_code": 1,
"entry": "browser.rml",
"icon": "/system/icons/browser.tga",
"description": "Web browser application",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [
"network"
],
"min_api_version": 1
}

View File

@@ -0,0 +1,368 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Camera</title>
<style>
.camera-screen {
background-color: #000000;
}
/* Top Controls */
.camera-top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
position: absolute;
top: 36px;
left: 0;
right: 0;
z-index: 10;
}
.camera-btn {
width: 48px;
height: 48px;
border-radius: 24px;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.camera-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.camera-btn img {
width: 28px;
height: 28px;
}
/* Viewfinder */
.viewfinder-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.viewfinder {
width: 100%;
height: 100%;
background-color: #1a1a1a;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.viewfinder-placeholder {
color: #666666;
font-size: 18px;
text-align: center;
}
.viewfinder-placeholder-icon {
font-size: 64px;
margin-bottom: 16px;
color: #444444;
}
/* Grid Overlay */
.grid-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.grid-line-h, .grid-line-v {
position: absolute;
background-color: rgba(255, 255, 255, 0.2);
}
.grid-line-h {
height: 1px;
left: 0;
right: 0;
}
.grid-line-h.h1 { top: 33.33%; }
.grid-line-h.h2 { top: 66.66%; }
.grid-line-v {
width: 1px;
top: 0;
bottom: 0;
}
.grid-line-v.v1 { left: 33.33%; }
.grid-line-v.v2 { left: 66.66%; }
/* Focus indicator */
.focus-indicator {
position: absolute;
width: 80px;
height: 80px;
border: 2px solid #FFFFFF;
border-radius: 8px;
top: 50%;
left: 50%;
margin-top: -40px;
margin-left: -40px;
pointer-events: none;
opacity: 0.8;
}
/* Camera Modes */
.camera-modes {
display: flex;
justify-content: center;
padding: 16px 0;
gap: 24px;
}
.camera-mode {
font-size: 16px;
color: #B3B3B3;
cursor: pointer;
padding: 8px;
}
.camera-mode.active {
color: #FFD700;
font-weight: 600;
}
.camera-mode:hover {
color: #FFFFFF;
}
/* Bottom Controls */
.camera-bottom-bar {
display: flex;
justify-content: space-around;
align-items: center;
padding: 20px 32px;
background-color: rgba(0, 0, 0, 0.6);
}
.gallery-preview {
width: 48px;
height: 48px;
border-radius: 8px;
background-color: #333333;
border: 2px solid #FFFFFF;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.gallery-preview img {
width: 28px;
height: 28px;
opacity: 0.7;
}
.capture-btn {
width: 72px;
height: 72px;
border-radius: 36px;
background-color: #FFFFFF;
border: 4px solid rgba(255, 255, 255, 0.3);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.capture-btn:hover {
transform: scale(1.05);
}
.capture-btn:active {
transform: scale(0.95);
background-color: #E0E0E0;
}
.capture-btn-inner {
width: 60px;
height: 60px;
border-radius: 30px;
background-color: #FFFFFF;
}
.switch-camera-btn {
width: 48px;
height: 48px;
border-radius: 24px;
background-color: rgba(255, 255, 255, 0.2);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.switch-camera-btn:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.switch-camera-btn img {
width: 28px;
height: 28px;
}
/* Indicators */
.flash-indicator {
position: absolute;
top: 100px;
left: 16px;
background-color: rgba(0, 0, 0, 0.4);
padding: 6px 12px;
border-radius: 12px;
font-size: 14px;
color: #FFFFFF;
}
.timer-indicator {
position: absolute;
top: 100px;
right: 16px;
background-color: rgba(0, 0, 0, 0.4);
padding: 6px 12px;
border-radius: 12px;
font-size: 14px;
color: #FFFFFF;
}
/* Zoom control */
.zoom-control {
position: absolute;
bottom: 200px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 12px;
}
.zoom-btn {
width: 40px;
height: 40px;
border-radius: 20px;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #FFFFFF;
cursor: pointer;
}
.zoom-level {
font-size: 16px;
color: #FFD700;
font-weight: 600;
min-width: 48px;
text-align: center;
}
</style>
</head>
<body class="app-screen camera-screen" onload="initLayout(document)">
<!-- System Status Bar (transparent) -->
<div class="system-status-bar" style="background-color: transparent; position: absolute; top: 0; left: 0; right: 0; z-index: 20;">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- Top Bar -->
<div class="camera-top-bar">
<div class="camera-btn" onclick="goBack()">
<img src="../../icons/close.tga"/>
</div>
<div style="display: flex; gap: 12px;">
<div class="camera-btn">
<img src="../../icons/flash.tga"/>
</div>
<div class="camera-btn">
<img src="../../icons/timer.tga"/>
</div>
<div class="camera-btn">
<img src="../../icons/settings.tga"/>
</div>
</div>
</div>
<!-- Viewfinder Area -->
<div class="viewfinder-container">
<div class="viewfinder" id="camera-viewfinder">
<div class="viewfinder-placeholder">
<div class="viewfinder-placeholder-icon">C</div>
<div>Camera Preview</div>
<div style="font-size: 14px; margin-top: 8px; color: #555555;">
Tap to focus
</div>
</div>
<!-- Grid Overlay -->
<div class="grid-overlay">
<div class="grid-line-h h1"></div>
<div class="grid-line-h h2"></div>
<div class="grid-line-v v1"></div>
<div class="grid-line-v v2"></div>
</div>
<!-- Focus Indicator -->
<div class="focus-indicator"></div>
</div>
<!-- Indicators -->
<div class="flash-indicator">Flash: Auto</div>
<div class="timer-indicator">Timer: Off</div>
<!-- Zoom Control -->
<div class="zoom-control">
<div class="zoom-btn">-</div>
<span class="zoom-level">1.0x</span>
<div class="zoom-btn">+</div>
</div>
</div>
<!-- Camera Modes -->
<div class="camera-modes">
<span class="camera-mode">Night</span>
<span class="camera-mode">Portrait</span>
<span class="camera-mode active">Photo</span>
<span class="camera-mode">Video</span>
<span class="camera-mode">More</span>
</div>
<!-- Bottom Bar -->
<div class="camera-bottom-bar">
<div class="gallery-preview">
<img src="../../icons/gallery.tga"/>
</div>
<div class="capture-btn" id="capture-button">
<div class="capture-btn-inner"></div>
</div>
<div class="switch-camera-btn">
<img src="../../icons/switch-camera.tga"/>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,19 @@
{
"id": "com.mosis.camera",
"name": "Camera",
"version": "1.0.0",
"version_code": 1,
"entry": "camera.rml",
"icon": "/system/icons/camera.tga",
"description": "Camera and photo capture",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [
"camera",
"storage"
],
"min_api_version": 1
}

View File

@@ -0,0 +1,161 @@
<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="../../scripts/phone.lua"></script>
<script src="../../scripts/contacts.lua"></script>
<title>Contact</title>
<style>
.contact-detail-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.contact-header {
background: linear-gradient(180deg, #1E1E1E 0%, #121212 100%);
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
}
.contact-avatar-large {
width: 96px;
height: 96px;
border-radius: 48px;
background-color: #BB86FC;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
color: #000000;
margin-bottom: 16px;
}
.contact-name-large {
font-size: 24px;
font-weight: 500;
color: #FFFFFF;
margin-bottom: 8px;
}
.contact-actions {
display: flex;
gap: 32px;
margin-top: 24px;
}
.contact-action {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
}
.contact-action-icon {
width: 56px;
height: 56px;
border-radius: 28px;
background-color: #BB86FC;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.contact-action-icon img {
width: 32px;
height: 32px;
pointer-events: none;
}
.contact-action-label {
font-size: 16px;
color: #B3B3B3;
}
.contact-info-section {
padding: 16px;
}
.contact-info-item {
display: flex;
align-items: center;
padding: 16px 0;
}
.contact-info-icon {
width: 28px;
height: 28px;
margin-right: 16px;
}
.contact-info-content {
flex: 1;
}
.contact-info-label {
font-size: 16px;
color: #B3B3B3;
}
.contact-info-value {
font-size: 16px;
color: #FFFFFF;
margin-top: 4px;
}
</style>
</head>
<body class="contact-detail-screen" onload="updateContactDetail()">
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()"><img src="../../icons/back.tga" style="width: 32px; height: 32px;"/></div>
<span class="app-bar-title">Contact</span>
<div class="btn-icon"><img src="../../icons/more.tga" style="width: 32px; height: 32px;"/></div>
</div>
<!-- Contact Header -->
<div class="contact-header">
<div class="contact-avatar-large" id="contact-avatar">?</div>
<div class="contact-name-large" id="contact-name">Contact Name</div>
<div class="contact-actions">
<div class="contact-action" id="call-action">
<div class="contact-action-icon" style="background-color: #4CAF50;">
<img src="../../icons/phone.tga"/>
</div>
<span class="contact-action-label">Call</span>
</div>
<div class="contact-action" id="message-action">
<div class="contact-action-icon" style="background-color: #2196F3;">
<img src="../../icons/message.tga"/>
</div>
<span class="contact-action-label">Message</span>
</div>
</div>
</div>
<!-- Contact Info -->
<div class="contact-info-section">
<div class="contact-info-item">
<img src="../../icons/phone.tga" class="contact-info-icon" style="width: 28px; height: 28px;"/>
<div class="contact-info-content">
<div class="contact-info-label">Mobile</div>
<div class="contact-info-value" id="contact-phone">+1 (555) 000-0000</div>
</div>
</div>
<div class="contact-info-item">
<img src="../../icons/message.tga" class="contact-info-icon" style="width: 28px; height: 28px;"/>
<div class="contact-info-content">
<div class="contact-info-label">Email</div>
<div class="contact-info-value" id="contact-email">email@example.com</div>
</div>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,321 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Contacts</title>
<style>
.contacts-list {
flex: 1;
overflow: auto;
}
.contact-letter {
padding: 8px 16px;
font-size: 18px;
font-weight: 500;
color: #BB86FC;
background-color: #1E1E1E;
position: sticky;
top: 0;
}
.contact-item {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
}
.contact-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.contact-item:active {
background-color: rgba(255, 255, 255, 0.1);
}
.contact-avatar {
width: 48px;
height: 48px;
border-radius: 24px;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #000000;
}
.contact-info {
flex: 1;
}
.contact-name {
font-size: 18px;
color: #FFFFFF;
}
.contact-phone {
font-size: 16px;
color: #B3B3B3;
margin-top: 2px;
}
.contact-call-btn {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 28px;
}
.contact-call-btn:hover {
background-color: rgba(76, 175, 80, 0.2);
}
.contact-call-btn:active {
background-color: rgba(76, 175, 80, 0.3);
}
.contact-call-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
/* Phone app bottom tabs */
.phone-tabs {
height: 72px;
background-color: #1E1E1E;
display: flex;
}
.phone-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
color: #666666;
}
.phone-tab:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.phone-tab.active {
color: #BB86FC;
}
.phone-tab img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.phone-tab span {
font-size: 14px;
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="contacts">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Contacts</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
<div class="app-bar-action">
<img src="../../icons/add.tga"/>
</div>
</div>
</div>
<!-- Search Bar -->
<div class="search-bar">
<img src="../../icons/search.tga" class="search-icon" style="width: 24px; height: 24px;"/>
<input class="search-input" type="text" placeholder="Search contacts"/>
</div>
<!-- Contacts List -->
<div class="app-content">
<div class="contacts-list">
<!-- A -->
<div class="contact-letter">A</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #E91E63;">A</div>
<div class="contact-info">
<div class="contact-name">Alice Johnson</div>
<div class="contact-phone">+1 555-0101</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #9C27B0;">A</div>
<div class="contact-info">
<div class="contact-name">Andrew Smith</div>
<div class="contact-phone">+1 555-0102</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- B -->
<div class="contact-letter">B</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #2196F3;">B</div>
<div class="contact-info">
<div class="contact-name">Bob Williams</div>
<div class="contact-phone">+1 555-0201</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- C -->
<div class="contact-letter">C</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #4CAF50;">C</div>
<div class="contact-info">
<div class="contact-name">Carol Davis</div>
<div class="contact-phone">+1 555-0301</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #FF9800;">C</div>
<div class="contact-info">
<div class="contact-name">Chris Miller</div>
<div class="contact-phone">+1 555-0302</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- D -->
<div class="contact-letter">D</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #F44336;">D</div>
<div class="contact-info">
<div class="contact-name">David Brown</div>
<div class="contact-phone">+1 555-0401</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- E -->
<div class="contact-letter">E</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #00BCD4;">E</div>
<div class="contact-info">
<div class="contact-name">Emma Wilson</div>
<div class="contact-phone">+1 555-0501</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- J -->
<div class="contact-letter">J</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #673AB7;">J</div>
<div class="contact-info">
<div class="contact-name">John Doe</div>
<div class="contact-phone">+1 555-1234</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- M -->
<div class="contact-letter">M</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #3F51B5;">M</div>
<div class="contact-info">
<div class="contact-name">Mary Taylor</div>
<div class="contact-phone">+1 555-0601</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #009688;">M</div>
<div class="contact-info">
<div class="contact-name">Michael Lee</div>
<div class="contact-phone">+1 555-0602</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- S -->
<div class="contact-letter">S</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #795548;">S</div>
<div class="contact-info">
<div class="contact-name">Sarah Anderson</div>
<div class="contact-phone">+1 555-0701</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
</div>
</div>
<!-- FAB -->
<div class="btn-fab">
<img src="../../icons/add.tga" style="width: 32px; height: 32px;"/>
</div>
<!-- Phone App Bottom Tabs -->
<div class="phone-tabs">
<div class="phone-tab" onclick="navigateTo('dialer')">
<img src="../../icons/dialpad.tga"/>
<span>Keypad</span>
</div>
<div class="phone-tab">
<img src="../../icons/history.tga"/>
<span>Recent</span>
</div>
<div class="phone-tab active">
<img src="../../icons/contacts.tga"/>
<span>Contacts</span>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,18 @@
{
"id": "com.mosis.contacts",
"name": "Contacts",
"version": "1.0.0",
"version_code": 1,
"entry": "contacts.rml",
"icon": "/system/icons/contacts.tga",
"description": "Contact list and management",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [
"contacts"
],
"min_api_version": 1
}

View File

@@ -0,0 +1,126 @@
<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="../../scripts/phone.lua"></script>
<title>Calling</title>
<style>
.calling-screen {
width: 100%;
height: 100%;
background: linear-gradient(180deg, #1a237e 0%, #121212 100%);
display: flex;
flex-direction: column;
align-items: center;
}
.calling-status {
margin-top: 80px;
font-size: 18px;
color: #4CAF50;
text-transform: uppercase;
letter-spacing: 2px;
}
.calling-name {
margin-top: 24px;
font-size: 32px;
font-weight: 300;
color: #FFFFFF;
}
.calling-number {
margin-top: 8px;
font-size: 16px;
color: #B3B3B3;
}
.calling-avatar {
margin-top: 48px;
width: 120px;
height: 120px;
border-radius: 60px;
background-color: #BB86FC;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: #000000;
}
.calling-actions {
position: absolute;
bottom: 120px;
display: flex;
gap: 48px;
}
.call-action-btn {
width: 64px;
height: 64px;
border-radius: 32px;
background-color: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.call-action-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.call-action-btn img {
width: 32px;
height: 32px;
pointer-events: none;
}
.end-call-btn {
position: absolute;
bottom: 40px;
width: 72px;
height: 72px;
border-radius: 36px;
background-color: #F44336;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.end-call-btn:hover {
background-color: #E53935;
}
.end-call-btn img {
width: 32px;
height: 32px;
pointer-events: none;
transform: rotate(135deg);
}
</style>
</head>
<body class="calling-screen">
<div class="calling-status">Calling...</div>
<div class="calling-name" id="calling-name">Unknown</div>
<div class="calling-number" id="calling-number"></div>
<div class="calling-avatar" id="calling-avatar">?</div>
<div class="calling-actions">
<div class="call-action-btn">
<img src="../../icons/dialpad.tga"/>
</div>
<div class="call-action-btn">
<img src="../../icons/contacts.tga"/>
</div>
</div>
<div class="end-call-btn" onclick="endCall()">
<img src="../../icons/phone.tga"/>
</div>
</body>
</rml>

View File

@@ -0,0 +1,157 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Phone</title>
<style>
.dialer-content {
flex: 1;
display: flex;
flex-direction: column;
}
/* Phone app bottom tabs */
.phone-tabs {
height: 72px;
background-color: #1E1E1E;
display: flex;
}
.phone-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
color: #666666;
}
.phone-tab:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.phone-tab.active {
color: #BB86FC;
}
.phone-tab img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.phone-tab span {
font-size: 14px;
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="phone">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Phone</span>
</div>
<!-- Dialer Content -->
<div class="dialer-content">
<!-- Dial Display -->
<div class="dial-display">{{ dial_number }}</div>
<!-- Dial Pad -->
<div class="dial-pad">
<div class="dial-key" data-event-click="dial_press('1')">
<span class="dial-key-number">1</span>
<span class="dial-key-letters"></span>
</div>
<div class="dial-key" data-event-click="dial_press('2')">
<span class="dial-key-number">2</span>
<span class="dial-key-letters">ABC</span>
</div>
<div class="dial-key" data-event-click="dial_press('3')">
<span class="dial-key-number">3</span>
<span class="dial-key-letters">DEF</span>
</div>
<div class="dial-key" data-event-click="dial_press('4')">
<span class="dial-key-number">4</span>
<span class="dial-key-letters">GHI</span>
</div>
<div class="dial-key" data-event-click="dial_press('5')">
<span class="dial-key-number">5</span>
<span class="dial-key-letters">JKL</span>
</div>
<div class="dial-key" data-event-click="dial_press('6')">
<span class="dial-key-number">6</span>
<span class="dial-key-letters">MNO</span>
</div>
<div class="dial-key" data-event-click="dial_press('7')">
<span class="dial-key-number">7</span>
<span class="dial-key-letters">PQRS</span>
</div>
<div class="dial-key" data-event-click="dial_press('8')">
<span class="dial-key-number">8</span>
<span class="dial-key-letters">TUV</span>
</div>
<div class="dial-key" data-event-click="dial_press('9')">
<span class="dial-key-number">9</span>
<span class="dial-key-letters">WXYZ</span>
</div>
<div class="dial-key" data-event-click="dial_press('*')">
<span class="dial-key-number">*</span>
<span class="dial-key-letters"></span>
</div>
<div class="dial-key" data-event-click="dial_press('0')">
<span class="dial-key-number">0</span>
<span class="dial-key-letters">+</span>
</div>
<div class="dial-key" data-event-click="dial_press('#')">
<span class="dial-key-number">#</span>
<span class="dial-key-letters"></span>
</div>
</div>
<!-- Call Actions -->
<div class="dial-actions">
<div style="width: 56px;"></div>
<div class="dial-call-btn" data-event-click="make_call()">
<img src="../../icons/call_small.tga" style="width: 32px; height: 32px; pointer-events: none;"/>
</div>
<div class="btn-icon" data-event-click="dial_backspace()" style="width: 56px; height: 56px;">
<img src="../../icons/backspace.tga" style="width: 32px; height: 32px; pointer-events: none;"/>
</div>
</div>
</div>
<!-- Phone App Bottom Tabs -->
<div class="phone-tabs">
<div class="phone-tab active">
<img src="../../icons/dialpad.tga"/>
<span>Keypad</span>
</div>
<div class="phone-tab">
<img src="../../icons/history.tga"/>
<span>Recent</span>
</div>
<div class="phone-tab" onclick="navigateTo('contacts')">
<img src="../../icons/contacts.tga"/>
<span>Contacts</span>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,18 @@
{
"id": "com.mosis.dialer",
"name": "Phone",
"version": "1.0.0",
"version_code": 1,
"entry": "dialer.rml",
"icon": "/system/icons/phone.tga",
"description": "Phone dialer and call interface",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [
"phone"
],
"min_api_version": 1
}

View 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

View 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>

View 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>

View 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
}

View File

@@ -0,0 +1,164 @@
<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="../../scripts/messages.lua"></script>
<title>Chat</title>
<style>
.chat-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.chat-header {
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #1E1E1E;
}
.chat-avatar {
width: 48px;
height: 48px;
border-radius: 24px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #000000;
}
.chat-header-info {
flex: 1;
}
.chat-header-name {
font-size: 16px;
font-weight: 500;
color: #FFFFFF;
}
.chat-header-status {
font-size: 16px;
color: #4CAF50;
}
.chat-messages {
flex: 1;
overflow: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.message-bubble {
max-width: 75%;
padding: 12px 16px;
border-radius: 18px;
font-size: 18px;
line-height: 1.4;
}
.message-sent {
align-self: flex-end;
background-color: #BB86FC;
color: #000000;
border-bottom-right-radius: 4px;
}
.message-received {
align-self: flex-start;
background-color: #2D2D2D;
color: #FFFFFF;
border-bottom-left-radius: 4px;
}
.message-time {
font-size: 16px;
color: #666666;
margin-top: 4px;
text-align: right;
}
.message-time-received {
text-align: left;
}
.chat-input-bar {
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #1E1E1E;
gap: 8px;
}
.chat-input {
flex: 1;
padding: 12px 18px;
background-color: #2D2D2D;
border-radius: 24px;
color: #FFFFFF;
font-size: 18px;
}
.chat-send-btn {
width: 56px;
height: 56px;
border-radius: 28px;
background-color: #BB86FC;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.chat-send-btn:hover {
background-color: #9C64FC;
}
.chat-send-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
</style>
</head>
<body class="chat-screen">
<!-- Chat Header -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()"><img src="../../icons/back.tga" style="width: 32px; height: 32px;"/></div>
<div class="chat-avatar" id="chat-avatar" style="background-color: #4CAF50;">J</div>
<div class="chat-header-info">
<div class="chat-header-name" id="chat-name">John Wilson</div>
<div class="chat-header-status">Online</div>
</div>
<div class="btn-icon"><img src="../../icons/phone.tga" style="width: 32px; height: 32px;"/></div>
<div class="btn-icon"><img src="../../icons/more.tga" style="width: 32px; height: 32px;"/></div>
</div>
<!-- Messages -->
<div class="chat-messages" id="chat-messages">
<div class="message-bubble message-received">Hey!</div>
<div class="message-bubble message-received">What are you up to?</div>
<div class="message-bubble message-sent">Not much, just working</div>
<div class="message-bubble message-received">Cool! There's a party at Mike's tonight</div>
<div class="message-bubble message-received">Hey, are you coming to the party tonight?</div>
</div>
<!-- Input Bar -->
<div class="chat-input-bar">
<div class="btn-icon"><img src="../../icons/add.tga" style="width: 32px; height: 32px;"/></div>
<input class="chat-input" type="text" placeholder="Type a message..." id="message-input"/>
<div class="chat-send-btn">
<img src="../../icons/send.tga"/>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,18 @@
{
"id": "com.mosis.messages",
"name": "Messages",
"version": "1.0.0",
"version_code": 1,
"entry": "messages.rml",
"icon": "/system/icons/message.tga",
"description": "SMS and messaging application",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [
"sms"
],
"min_api_version": 1
}

View File

@@ -0,0 +1,220 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Messages</title>
<style>
.conversations-list {
flex: 1;
overflow: auto;
}
.conversation-item {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
}
.conversation-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.conversation-item:active {
background-color: rgba(255, 255, 255, 0.1);
}
.conversation-avatar {
width: 56px;
height: 56px;
border-radius: 28px;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
color: #000000;
}
.conversation-content {
flex: 1;
min-width: 0;
}
.conversation-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.conversation-name {
font-size: 18px;
font-weight: 500;
color: #FFFFFF;
}
.conversation-time {
font-size: 14px;
color: #666666;
}
.conversation-preview {
font-size: 16px;
color: #B3B3B3;
margin-top: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.conversation-unread {
width: 24px;
height: 24px;
border-radius: 12px;
background-color: #BB86FC;
color: #000000;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
margin-left: 8px;
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="messages">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Messages</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Conversations List -->
<div class="app-content with-nav">
<div class="conversations-list">
<!-- Alice -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #E91E63;">A</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Alice Johnson</span>
<span class="conversation-time">2:34 PM</span>
</div>
<div class="conversation-preview">Hey! Are you coming to the party tonight?</div>
</div>
<div class="conversation-unread">2</div>
</div>
<!-- Bob -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #2196F3;">B</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Bob Williams</span>
<span class="conversation-time">1:15 PM</span>
</div>
<div class="conversation-preview">Thanks for the help yesterday!</div>
</div>
</div>
<!-- Carol -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #4CAF50;">C</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Carol Davis</span>
<span class="conversation-time">Yesterday</span>
</div>
<div class="conversation-preview">The meeting has been rescheduled to Friday</div>
</div>
</div>
<!-- David -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #FF9800;">D</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">David Brown</span>
<span class="conversation-time">Yesterday</span>
</div>
<div class="conversation-preview">Can you send me the files?</div>
</div>
<div class="conversation-unread">1</div>
</div>
<!-- Emma -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #9C27B0;">E</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Emma Wilson</span>
<span class="conversation-time">Mon</span>
</div>
<div class="conversation-preview">See you at the coffee shop!</div>
</div>
</div>
<!-- Frank -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #00BCD4;">F</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Frank Miller</span>
<span class="conversation-time">Sun</span>
</div>
<div class="conversation-preview">Great game last night!</div>
</div>
</div>
<!-- Grace -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #673AB7;">G</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Grace Lee</span>
<span class="conversation-time">Sat</span>
</div>
<div class="conversation-preview">Happy birthday! 🎂</div>
</div>
</div>
</div>
</div>
<!-- FAB -->
<div class="btn-fab">
<img src="../../icons/add.tga" style="width: 32px; height: 32px;"/>
</div>
<!-- System Navigation Bar -->
<div class="system-nav-bar">
<div class="system-nav-btn" onclick="onBackPressed()">
<img src="../../icons/back.tga"/>
</div>
<div class="system-nav-home" onclick="onHomePressed()"></div>
<div class="system-nav-btn" onclick="onRecentPressed()">
<img src="../../icons/menu.tga"/>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,18 @@
{
"id": "com.mosis.music",
"name": "Music",
"version": "1.0.0",
"version_code": 1,
"entry": "music.rml",
"icon": "/system/icons/music.tga",
"description": "Music player application",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [
"storage"
],
"min_api_version": 1
}

View File

@@ -0,0 +1,415 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Music</title>
<style>
.music-content {
flex: 1;
overflow: auto;
}
.mini-player {
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #282828;
border-top-width: 1px;
border-top-color: #333333;
}
.mini-player-art {
width: 48px;
height: 48px;
border-radius: 4px;
background-color: #667eea;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #FFFFFF;
}
.mini-player-info {
flex: 1;
}
.mini-player-title {
font-size: 16px;
color: #FFFFFF;
font-weight: 500;
}
.mini-player-artist {
font-size: 14px;
color: #B3B3B3;
margin-top: 2px;
}
.mini-control-btn {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 24px;
}
.mini-control-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.mini-control-btn img {
width: 28px;
height: 28px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 16px 12px 16px;
}
.section-title {
font-size: 20px;
font-weight: 700;
color: #FFFFFF;
}
.section-action {
font-size: 14px;
font-weight: 600;
color: #B3B3B3;
cursor: pointer;
}
.section-action:hover {
color: #FFFFFF;
}
.recent-row {
display: flex;
overflow-x: auto;
padding: 0 16px;
gap: 16px;
}
.recent-item {
min-width: 120px;
cursor: pointer;
}
.recent-item:hover {
opacity: 0.9;
}
.recent-art {
width: 120px;
height: 120px;
border-radius: 8px;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
color: #FFFFFF;
}
.recent-title {
font-size: 16px;
color: #FFFFFF;
font-weight: 500;
margin-bottom: 4px;
}
.recent-subtitle {
font-size: 14px;
color: #B3B3B3;
}
.quick-access {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 0 16px;
}
.quick-card {
width: 48%;
display: flex;
align-items: center;
background-color: #282828;
border-radius: 4px;
padding: 0;
height: 56px;
overflow: hidden;
cursor: pointer;
}
.quick-card:hover {
background-color: #333333;
}
.quick-card-art {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #FFFFFF;
}
.quick-card-title {
font-size: 14px;
font-weight: 600;
color: #FFFFFF;
padding: 0 12px;
}
.playlist-item {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
}
.playlist-item:hover {
background-color: #1E1E1E;
}
.playlist-art {
width: 56px;
height: 56px;
border-radius: 4px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #FFFFFF;
}
.playlist-info {
flex: 1;
}
.playlist-title {
font-size: 16px;
color: #FFFFFF;
font-weight: 500;
}
.playlist-meta {
font-size: 14px;
color: #B3B3B3;
margin-top: 4px;
}
.music-bottom-nav {
display: flex;
height: 56px;
background-color: #1E1E1E;
border-top-width: 1px;
border-top-color: #282828;
}
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
color: #B3B3B3;
}
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.nav-item.active {
color: #FFFFFF;
}
.nav-item img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.nav-item span {
font-size: 14px;
}
.bg-gradient-1 { background-color: #667eea; }
.bg-gradient-2 { background-color: #f093fb; }
.bg-gradient-3 { background-color: #4facfe; }
.bg-gradient-4 { background-color: #43e97b; }
.bg-gradient-5 { background-color: #fa709a; }
.bg-solid-purple { background-color: #7c3aed; }
.bg-solid-red { background-color: #dc2626; }
.bg-solid-green { background-color: #16a34a; }
.bg-solid-blue { background-color: #2563eb; }
</style>
</head>
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Music</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Content -->
<div class="music-content">
<!-- Good Afternoon Section -->
<div class="section-header">
<span class="section-title">Good afternoon</span>
</div>
<!-- Quick Access Grid -->
<div class="quick-access">
<div class="quick-card">
<div class="quick-card-art bg-solid-red">L</div>
<span class="quick-card-title">Liked Songs</span>
</div>
<div class="quick-card">
<div class="quick-card-art bg-gradient-1">D</div>
<span class="quick-card-title">Daily Mix 1</span>
</div>
<div class="quick-card">
<div class="quick-card-art bg-solid-green">R</div>
<span class="quick-card-title">Release Radar</span>
</div>
<div class="quick-card">
<div class="quick-card-art bg-gradient-2">C</div>
<span class="quick-card-title">Chill Vibes</span>
</div>
<div class="quick-card">
<div class="quick-card-art bg-solid-blue">W</div>
<span class="quick-card-title">Workout Mix</span>
</div>
<div class="quick-card">
<div class="quick-card-art bg-gradient-3">F</div>
<span class="quick-card-title">Focus Flow</span>
</div>
</div>
<!-- Recently Played -->
<div class="section-header">
<span class="section-title">Recently Played</span>
<span class="section-action">SEE ALL</span>
</div>
<div class="recent-row">
<div class="recent-item">
<div class="recent-art bg-gradient-4">P</div>
<div class="recent-title">Pop Hits</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item">
<div class="recent-art bg-gradient-5">E</div>
<div class="recent-title">Electronic</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item">
<div class="recent-art bg-gradient-1">J</div>
<div class="recent-title">Jazz Classics</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item">
<div class="recent-art bg-gradient-2">R</div>
<div class="recent-title">Rock Legends</div>
<div class="recent-subtitle">Playlist</div>
</div>
</div>
<!-- Made For You -->
<div class="section-header">
<span class="section-title">Made For You</span>
<span class="section-action">SEE ALL</span>
</div>
<div class="playlist-item">
<div class="playlist-art bg-gradient-3">1</div>
<div class="playlist-info">
<div class="playlist-title">Daily Mix 1</div>
<div class="playlist-meta">Based on your listening</div>
</div>
</div>
<div class="playlist-item">
<div class="playlist-art bg-gradient-4">2</div>
<div class="playlist-info">
<div class="playlist-title">Daily Mix 2</div>
<div class="playlist-meta">Electronic, Ambient, Chill</div>
</div>
</div>
<div class="playlist-item">
<div class="playlist-art bg-gradient-5">D</div>
<div class="playlist-info">
<div class="playlist-title">Discover Weekly</div>
<div class="playlist-meta">Your weekly mixtape</div>
</div>
</div>
</div>
<!-- Mini Player -->
<div class="mini-player">
<div class="mini-player-art">M</div>
<div class="mini-player-info">
<div class="mini-player-title">Midnight City</div>
<div class="mini-player-artist">M83</div>
</div>
<div style="display: flex; gap: 4px;">
<div class="mini-control-btn">
<img src="../../icons/heart.tga"/>
</div>
<div class="mini-control-btn">
<img src="../../icons/play.tga"/>
</div>
</div>
</div>
<!-- Bottom Navigation -->
<div class="music-bottom-nav">
<div class="nav-item active">
<img src="../../icons/home.tga"/>
<span>Home</span>
</div>
<div class="nav-item">
<img src="../../icons/search.tga"/>
<span>Search</span>
</div>
<div class="nav-item">
<img src="../../icons/library.tga"/>
<span>Library</span>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,16 @@
{
"id": "com.mosis.settings",
"name": "Settings",
"version": "1.0.0",
"version_code": 1,
"entry": "settings.rml",
"icon": "/system/icons/settings.tga",
"description": "System settings and configuration",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [],
"min_api_version": 1
}

View File

@@ -0,0 +1,360 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Settings</title>
<style>
.settings-list {
flex: 1;
overflow: auto;
width: 100%;
display: flex;
flex-direction: column;
}
.settings-section {
margin-bottom: 8px;
width: 100%;
display: flex;
flex-direction: column;
}
.settings-header {
padding: 16px 16px 8px 16px;
font-size: 18px;
font-weight: 500;
color: #BB86FC;
}
.settings-item {
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
padding: 16px;
cursor: pointer;
background-color: #1E1E1E;
overflow: hidden;
}
.settings-item:hover {
background-color: #252525;
}
.settings-item + .settings-item {
border-top: 1px #333333;
}
.settings-icon {
width: 48px;
height: 48px;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.settings-icon img {
width: 32px;
height: 32px;
opacity: 0.7;
}
.settings-content {
flex: 1;
}
.settings-title {
font-size: 18px;
color: #FFFFFF;
}
.settings-subtitle {
font-size: 16px;
color: #B3B3B3;
margin-top: 4px;
}
.settings-action {
font-size: 20px;
color: #666666;
padding: 8px;
}
.settings-toggle {
width: 56px;
height: 32px;
border-radius: 16px;
background-color: #666666;
cursor: pointer;
position: relative;
}
.settings-toggle.active {
background-color: rgba(187, 134, 252, 0.5);
}
.settings-toggle-thumb {
width: 28px;
height: 28px;
border-radius: 14px;
background-color: #B3B3B3;
position: absolute;
top: 2px;
left: 2px;
}
.settings-toggle.active .settings-toggle-thumb {
background-color: #BB86FC;
left: 26px;
}
.user-card {
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
padding: 20px 16px;
background-color: #1E1E1E;
margin-bottom: 8px;
cursor: pointer;
}
.user-card:hover {
background-color: #252525;
}
.user-avatar {
width: 64px;
height: 64px;
border-radius: 32px;
background-color: #BB86FC;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
color: #000000;
}
.user-info {
flex: 1;
}
.user-name {
font-size: 20px;
font-weight: 500;
color: #FFFFFF;
}
.user-email {
font-size: 16px;
color: #B3B3B3;
margin-top: 4px;
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Settings</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Settings List -->
<div class="app-content with-nav">
<div class="settings-list">
<!-- User Card -->
<div class="user-card">
<div class="user-avatar">U</div>
<div class="user-info">
<div class="user-name">User</div>
<div class="user-email">user@mosis.local</div>
</div>
<span class="settings-action">></span>
</div>
<!-- Network Section -->
<div class="settings-section">
<div class="settings-header">Network</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/wifi.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Wi-Fi</div>
<div class="settings-subtitle">Connected to MosisNetwork</div>
</div>
<div class="settings-toggle active">
<div class="settings-toggle-thumb"></div>
</div>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/signal.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Bluetooth</div>
<div class="settings-subtitle">Off</div>
</div>
<div class="settings-toggle">
<div class="settings-toggle-thumb"></div>
</div>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/signal.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Airplane Mode</div>
</div>
<div class="settings-toggle">
<div class="settings-toggle-thumb"></div>
</div>
</div>
</div>
<!-- Device Section -->
<div class="settings-section">
<div class="settings-header">Device</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/settings.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Display</div>
<div class="settings-subtitle">Brightness, wallpaper, sleep</div>
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/music.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Sound</div>
<div class="settings-subtitle">Volume, ringtone, vibration</div>
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/message.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Notifications</div>
<div class="settings-subtitle">App notifications, Do not disturb</div>
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/battery.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Battery</div>
<div class="settings-subtitle">85% - 4h 30m remaining</div>
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/files.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Storage</div>
<div class="settings-subtitle">32 GB of 128 GB used</div>
</div>
<span class="settings-action">></span>
</div>
</div>
<!-- Privacy Section -->
<div class="settings-section">
<div class="settings-header">Privacy &amp; Security</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/account.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Lock Screen</div>
<div class="settings-subtitle">PIN, pattern, fingerprint</div>
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/account.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Privacy</div>
<div class="settings-subtitle">Permissions, account activity</div>
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/maps.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Location</div>
<div class="settings-subtitle">On - High accuracy</div>
</div>
<div class="settings-toggle active">
<div class="settings-toggle-thumb"></div>
</div>
</div>
</div>
<!-- About Section -->
<div class="settings-section">
<div class="settings-header">About</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/phone.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">About Phone</div>
<div class="settings-subtitle">Mosis Virtual Phone v1.0</div>
</div>
<span class="settings-action">></span>
</div>
</div>
</div>
</div>
<!-- System Navigation Bar -->
<div class="system-nav-bar">
<div class="system-nav-btn" onclick="onBackPressed()">
<img src="../../icons/back.tga"/>
</div>
<div class="system-nav-home" onclick="onHomePressed()"></div>
<div class="system-nav-btn" onclick="onRecentPressed()">
<img src="../../icons/menu.tga"/>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,19 @@
{
"id": "com.mosis.store",
"name": "Mosis Store",
"version": "1.0.0",
"version_code": 1,
"entry": "store.rml",
"icon": "/system/icons/store.tga",
"description": "App store for downloading and installing apps",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"is_system_app": true,
"permissions": [
"network",
"storage"
],
"min_api_version": 1
}

View File

@@ -0,0 +1,394 @@
-- store.lua - App Store system app logic
-- Milestone 10: Device-Side App Management
-- State
local state = {
screen = "home", -- home, games, updates, search, detail
installed = {}, -- Installed apps from mosis.apps
updates = {}, -- Available updates
featured = {}, -- Featured apps from store API
categories = {}, -- Category list
search_query = "", -- Current search
selected_app = nil, -- Selected app for detail view
is_loading = false,
error_message = nil
}
-- Store API configuration
local STORE_API = "https://portal.mosis.dev/store"
-- ============================================================================
-- Initialization
-- ============================================================================
function init()
print("[Store] Initializing...")
-- Load installed apps
refreshInstalledApps()
-- Check for updates
checkForUpdates()
-- Fetch featured apps (async)
fetchFeaturedApps()
end
function refreshInstalledApps()
if mosis and mosis.apps then
state.installed = mosis.apps.getInstalled() or {}
print("[Store] Loaded " .. #state.installed .. " installed apps")
else
print("[Store] Warning: mosis.apps API not available")
state.installed = {}
end
end
function checkForUpdates()
if mosis and mosis.apps then
state.updates = mosis.apps.checkUpdates() or {}
print("[Store] Found " .. #state.updates .. " updates")
updateBadge()
end
end
function updateBadge()
-- Update the updates tab badge
local badge = document:GetElementById("updates-badge")
if badge then
if #state.updates > 0 then
badge.inner_rml = tostring(#state.updates)
badge.style.display = "block"
else
badge.style.display = "none"
end
end
end
-- ============================================================================
-- API Calls
-- ============================================================================
function fetchFeaturedApps()
state.is_loading = true
-- TODO: Make HTTP request to STORE_API
-- For now, use placeholder data
state.featured = {
{
id = "com.mosis.weather",
name = "Weather Pro",
category = "Weather",
rating = 4.8,
downloads = 125000,
size = 15728640, -- 15 MB
description = "Beautiful forecasts for your virtual world",
icon = "W",
color = "#2196F3"
},
{
id = "com.mosis.notes",
name = "Notes",
category = "Productivity",
rating = 4.7,
downloads = 89000,
size = 8388608, -- 8 MB
description = "Simple note-taking app",
icon = "N",
color = "#03DAC6"
}
}
state.is_loading = false
render()
end
function searchApps(query)
state.search_query = query
state.screen = "search"
if query == "" then
state.screen = "home"
render()
return
end
state.is_loading = true
render()
-- TODO: Make HTTP request to STORE_API/search
-- For now, filter featured apps
local results = {}
local lower_query = query:lower()
for _, app in ipairs(state.featured) do
if app.name:lower():find(lower_query) or
app.category:lower():find(lower_query) then
table.insert(results, app)
end
end
state.search_results = results
state.is_loading = false
render()
end
-- ============================================================================
-- Installation
-- ============================================================================
function installApp(app_id, download_url, signature)
print("[Store] Installing: " .. app_id)
showProgress(app_id)
if mosis and mosis.apps then
mosis.apps.install(download_url or "", signature or "", function(progress)
updateProgress(progress)
if progress.stage == "complete" then
hideProgress()
showToast("App installed successfully!")
refreshInstalledApps()
render()
elseif progress.stage == "failed" then
hideProgress()
showError("Installation failed: " .. (progress.error or "Unknown error"))
end
end)
else
hideProgress()
showError("App installation not available")
end
end
function uninstallApp(package_id)
print("[Store] Uninstalling: " .. package_id)
if mosis and mosis.apps then
local success = mosis.apps.uninstall(package_id)
if success then
showToast("App uninstalled")
refreshInstalledApps()
render()
else
showError("Failed to uninstall app")
end
end
end
function openApp(package_id)
print("[Store] Launching: " .. package_id)
if mosis and mosis.apps then
mosis.apps.launch(package_id)
end
end
function updateApp(package_id)
print("[Store] Updating: " .. package_id)
-- Find update info
for _, update in ipairs(state.updates) do
if update.package_id == package_id then
installApp(package_id, update.download_url, update.signature)
return
end
end
showError("No update available for this app")
end
function updateAllApps()
print("[Store] Updating all apps...")
for _, update in ipairs(state.updates) do
-- Queue updates (in a real implementation, this would be sequential)
installApp(update.package_id, update.download_url, update.signature)
end
end
-- ============================================================================
-- UI Helpers
-- ============================================================================
function isInstalled(package_id)
for _, app in ipairs(state.installed) do
if app.package_id == package_id then
return true
end
end
return false
end
function hasUpdate(package_id)
for _, update in ipairs(state.updates) do
if update.package_id == package_id then
return true
end
end
return false
end
function formatSize(bytes)
if bytes >= 1048576 then
return string.format("%.1f MB", bytes / 1048576)
elseif bytes >= 1024 then
return string.format("%.0f KB", bytes / 1024)
else
return bytes .. " B"
end
end
function formatDownloads(count)
if count >= 1000000 then
return string.format("%.1fM", count / 1000000)
elseif count >= 1000 then
return string.format("%.0fK", count / 1000)
else
return tostring(count)
end
end
-- ============================================================================
-- Progress Dialog
-- ============================================================================
function showProgress(app_name)
local dialog = document:GetElementById("progress-dialog")
if dialog then
dialog.style.display = "flex"
local title = document:GetElementById("progress-title")
if title then
title.inner_rml = "Installing " .. (app_name or "App")
end
end
end
function updateProgress(progress)
local bar = document:GetElementById("progress-bar")
if bar then
bar.style.width = (progress.progress * 100) .. "%"
end
local status = document:GetElementById("progress-status")
if status then
local stage_names = {
downloading = "Downloading...",
verifying = "Verifying...",
extracting = "Extracting...",
registering = "Registering...",
complete = "Complete!",
failed = "Failed"
}
status.inner_rml = stage_names[progress.stage] or progress.stage
end
end
function hideProgress()
local dialog = document:GetElementById("progress-dialog")
if dialog then
dialog.style.display = "none"
end
end
-- ============================================================================
-- Toast/Error Messages
-- ============================================================================
function showToast(message)
local toast = document:GetElementById("toast")
if toast then
toast.inner_rml = message
toast.style.display = "block"
-- Auto-hide after 3 seconds (would need timer API)
end
print("[Store] Toast: " .. message)
end
function showError(message)
state.error_message = message
local error_el = document:GetElementById("error-dialog")
if error_el then
local msg = document:GetElementById("error-message")
if msg then
msg.inner_rml = message
end
error_el.style.display = "flex"
end
print("[Store] Error: " .. message)
end
function hideError()
state.error_message = nil
local error_el = document:GetElementById("error-dialog")
if error_el then
error_el.style.display = "none"
end
end
-- ============================================================================
-- Navigation
-- ============================================================================
function showHome()
state.screen = "home"
setActiveTab("apps")
render()
end
function showGames()
state.screen = "games"
setActiveTab("games")
render()
end
function showUpdates()
state.screen = "updates"
setActiveTab("updates")
checkForUpdates()
render()
end
function showSearch()
state.screen = "search"
render()
end
function showAppDetail(app_id)
state.screen = "detail"
-- Find app in featured or installed
for _, app in ipairs(state.featured) do
if app.id == app_id then
state.selected_app = app
break
end
end
render()
end
function setActiveTab(tab)
local tabs = {"apps", "games", "updates"}
for _, t in ipairs(tabs) do
local el = document:GetElementById("nav-" .. t)
if el then
if t == tab then
el:SetClass("active", true)
else
el:SetClass("active", false)
end
end
end
end
-- ============================================================================
-- Rendering
-- ============================================================================
function render()
-- The RML is mostly static with dynamic data binding
-- In a full implementation, we'd update innerHTML of content areas
print("[Store] Rendering screen: " .. state.screen)
end
-- Initialize on load
init()

View File

@@ -0,0 +1,416 @@
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="store.lua"></script>
<title>Store</title>
<style>
.store-content {
flex: 1;
overflow: auto;
padding-bottom: 16px;
}
.store-search {
margin: 16px;
background-color: #2D2D2D;
border-radius: 24px;
padding: 12px 16px;
display: flex;
align-items: center;
cursor: pointer;
}
.store-search:hover {
background-color: #3D3D3D;
}
.store-search img {
width: 24px;
height: 24px;
margin-right: 12px;
opacity: 0.6;
}
.store-search-text {
font-size: 16px;
color: #B3B3B3;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 16px 8px 16px;
}
.section-title {
font-size: 18px;
font-weight: 500;
color: #FFFFFF;
}
.section-action {
font-size: 16px;
color: #BB86FC;
cursor: pointer;
}
.featured-banner {
margin: 0 16px 16px 16px;
height: 140px;
background-color: #7C3AED;
border-radius: 16px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: flex-end;
cursor: pointer;
}
.featured-banner:hover {
opacity: 0.95;
}
.featured-tag {
font-size: 14px;
color: rgba(255,255,255,0.7);
text-transform: uppercase;
margin-bottom: 8px;
}
.featured-title {
font-size: 22px;
font-weight: 600;
color: #FFFFFF;
margin-bottom: 4px;
}
.featured-subtitle {
font-size: 16px;
color: rgba(255,255,255,0.8);
}
.app-cards-row {
display: flex;
overflow-x: auto;
padding: 0 16px;
gap: 12px;
}
.app-card {
min-width: 130px;
background-color: #1E1E1E;
border-radius: 12px;
padding: 12px;
cursor: pointer;
}
.app-card:hover {
background-color: #252525;
}
.app-card-icon {
width: 56px;
height: 56px;
border-radius: 14px;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #000000;
}
.app-card-name {
font-size: 16px;
font-weight: 500;
color: #FFFFFF;
margin-bottom: 4px;
}
.app-card-category {
font-size: 14px;
color: #B3B3B3;
margin-bottom: 6px;
}
.app-card-rating {
font-size: 14px;
color: #B3B3B3;
}
.app-list-item {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
}
.app-list-item:hover {
background-color: #1E1E1E;
}
.app-list-icon {
width: 56px;
height: 56px;
border-radius: 12px;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #000000;
}
.app-list-info {
flex: 1;
}
.app-list-name {
font-size: 16px;
font-weight: 500;
color: #FFFFFF;
}
.app-list-meta {
font-size: 14px;
color: #B3B3B3;
margin-top: 4px;
}
.install-btn {
background-color: #BB86FC;
color: #000000;
font-size: 14px;
font-weight: 600;
padding: 10px 20px;
border-radius: 20px;
cursor: pointer;
}
.install-btn:hover {
background-color: #D4A5FF;
}
.install-btn.installed {
background-color: transparent;
color: #BB86FC;
}
.category-chips {
display: flex;
overflow-x: auto;
padding: 0 16px;
gap: 8px;
margin-bottom: 16px;
}
.category-chip {
background-color: #2D2D2D;
color: #FFFFFF;
font-size: 14px;
padding: 8px 16px;
border-radius: 20px;
white-space: nowrap;
cursor: pointer;
}
.category-chip:hover {
background-color: #3D3D3D;
}
.category-chip.active {
background-color: #BB86FC;
color: #000000;
}
.store-bottom-nav {
display: flex;
height: 56px;
background-color: #1E1E1E;
}
.store-nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
color: #B3B3B3;
}
.store-nav-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.store-nav-item.active {
color: #BB86FC;
}
.store-nav-item img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.store-nav-item span {
font-size: 14px;
}
.bg-purple { background-color: #BB86FC; }
.bg-teal { background-color: #03DAC6; }
.bg-orange { background-color: #FF9800; }
.bg-blue { background-color: #2196F3; }
.bg-green { background-color: #4CAF50; }
.bg-red { background-color: #F44336; }
.bg-pink { background-color: #E91E63; }
.bg-indigo { background-color: #3F51B5; }
</style>
</head>
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Mosis Store</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/account.tga"/>
</div>
</div>
</div>
<!-- Store Content -->
<div class="store-content">
<!-- Search Bar -->
<div class="store-search">
<img src="../../icons/search.tga"/>
<span class="store-search-text">Search apps &amp; games</span>
</div>
<!-- Featured Banner -->
<div class="featured-banner">
<span class="featured-tag">Featured</span>
<span class="featured-title">Weather Pro</span>
<span class="featured-subtitle">Beautiful forecasts for your virtual world</span>
</div>
<!-- Category Chips -->
<div class="category-chips">
<div class="category-chip active">For You</div>
<div class="category-chip">Games</div>
<div class="category-chip">Social</div>
<div class="category-chip">Productivity</div>
<div class="category-chip">Tools</div>
</div>
<!-- Recommended Section -->
<div class="section-header">
<span class="section-title">Recommended for You</span>
<span class="section-action">See all</span>
</div>
<div class="app-cards-row">
<div class="app-card">
<div class="app-card-icon bg-teal">N</div>
<div class="app-card-name">Notes</div>
<div class="app-card-category">Productivity</div>
<div class="app-card-rating">4.7</div>
</div>
<div class="app-card">
<div class="app-card-icon bg-orange">C</div>
<div class="app-card-name">Calculator</div>
<div class="app-card-category">Tools</div>
<div class="app-card-rating">4.5</div>
</div>
<div class="app-card">
<div class="app-card-icon bg-blue">W</div>
<div class="app-card-name">Weather</div>
<div class="app-card-category">Weather</div>
<div class="app-card-rating">4.8</div>
</div>
<div class="app-card">
<div class="app-card-icon bg-green">M</div>
<div class="app-card-name">Maps</div>
<div class="app-card-category">Navigation</div>
<div class="app-card-rating">4.6</div>
</div>
</div>
<!-- Top Apps Section -->
<div class="section-header">
<span class="section-title">Top Free Apps</span>
<span class="section-action">See all</span>
</div>
<div class="app-list-item">
<div class="app-list-icon bg-purple">S</div>
<div class="app-list-info">
<div class="app-list-name">Social Hub</div>
<div class="app-list-meta">Social - 12 MB - 4.9</div>
</div>
<div class="install-btn">Install</div>
</div>
<div class="app-list-item">
<div class="app-list-icon bg-red">G</div>
<div class="app-list-info">
<div class="app-list-name">Games Center</div>
<div class="app-list-meta">Games - 45 MB - 4.7</div>
</div>
<div class="install-btn">Install</div>
</div>
<div class="app-list-item">
<div class="app-list-icon bg-indigo">F</div>
<div class="app-list-info">
<div class="app-list-name">File Manager</div>
<div class="app-list-meta">Tools - 8 MB - 4.6</div>
</div>
<div class="install-btn installed">Open</div>
</div>
<div class="app-list-item">
<div class="app-list-icon bg-pink">M</div>
<div class="app-list-info">
<div class="app-list-name">Music Player</div>
<div class="app-list-meta">Music - 18 MB - 4.5</div>
</div>
<div class="install-btn">Install</div>
</div>
</div>
<!-- Bottom Navigation -->
<div class="store-bottom-nav">
<div class="store-nav-item active">
<img src="../../icons/home.tga"/>
<span>Apps</span>
</div>
<div class="store-nav-item">
<img src="../../icons/game.tga"/>
<span>Games</span>
</div>
<div class="store-nav-item">
<img src="../../icons/download.tga"/>
<span>Updates</span>
</div>
</div>
</body>
</rml>

BIN
base-apps/icons/account.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/add.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/back.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/backspace.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/battery.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/browser.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/calculator.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/calendar.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/call_small.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/camera.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/clock.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/close.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/contact_phone.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/contacts.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/dialpad.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/download.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/files.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/flash.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/forward.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/gallery.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/game.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/heart.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/history.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/home.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/library.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/maps.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/menu.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/message.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/more.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/music.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/notes.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/phone.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/play.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/refresh.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/search.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/send.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/settings.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/signal.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/store.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/switch-camera.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/timer.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/weather.tga LFS Normal file

Binary file not shown.

BIN
base-apps/icons/wifi.tga LFS Normal file

Binary file not shown.

View File

@@ -0,0 +1,103 @@
-- Layout System for Virtual Smartphone
-- Provides reusable UI component helpers
-- Requires navigation.lua to be loaded first
-- Icon paths (relative to assets/)
local ICON_PATH = "../../icons/"
-- Default icons
local icons = {
back = ICON_PATH .. "back.tga",
home = ICON_PATH .. "home.tga",
menu = ICON_PATH .. "menu.tga",
search = ICON_PATH .. "search.tga",
more = ICON_PATH .. "more.tga",
close = ICON_PATH .. "close.tga",
wifi = ICON_PATH .. "wifi.tga",
signal = ICON_PATH .. "signal.tga",
battery = ICON_PATH .. "battery.tga"
}
-- Get current time formatted as HH:MM
local function getCurrentTime()
-- In sandbox, we might not have os.date, use a default
if os and os.date then
return os.date("%H:%M")
end
return "12:30"
end
-- Update status bar time
function updateStatusTime(doc)
local timeEl = doc:GetElementById("status-time")
if timeEl then
timeEl.inner_rml = getCurrentTime()
end
end
-- Initialize status bar with current time
-- Call from document onload
function initStatusBar(doc)
updateStatusTime(doc)
-- Set up timer to update time every minute if timers are available
if setTimeout then
local function updateLoop()
updateStatusTime(doc)
setTimeout(updateLoop, 60000)
end
setTimeout(updateLoop, 60000)
end
end
-- Initialize app bar back button
-- Call from document onload
function initAppBar(doc)
local backBtn = doc:GetElementById("app-bar-back")
if backBtn then
-- Back button is handled via onclick in RML
-- This is for any additional setup
end
end
-- Initialize system navigation bar
-- Call from document onload
function initSystemNav(doc)
-- Navigation buttons are handled via onclick in RML
-- This is for any additional setup
end
-- Full layout initialization
-- Call from document onload: initLayout(document)
function initLayout(doc)
initStatusBar(doc)
initAppBar(doc)
initSystemNav(doc)
print("Layout initialized")
end
-- Handle back button press (for app bar or system nav)
function onBackPressed()
if canGoBack and canGoBack() then
goBack()
else
-- If at root, go home
if goHome then
goHome()
end
end
end
-- Handle home button press
function onHomePressed()
if goHome then
goHome()
end
end
-- Handle recent apps button press (placeholder)
function onRecentPressed()
print("Recent apps pressed (not implemented)")
end
print("Layout system loaded")

View File

@@ -0,0 +1,145 @@
-- Navigation System for Virtual Smartphone
-- Handles screen transitions and state management
-- Screen registry - maps screen names to RML file paths
local screens = {
home = "apps/home/home.rml",
lock = "apps/home/lock.rml",
dialer = "apps/dialer/dialer.rml",
calling = "apps/dialer/calling.rml",
contacts = "apps/contacts/contacts.rml",
contact_detail = "apps/contacts/contact_detail.rml",
messages = "apps/messages/messages.rml",
chat = "apps/messages/chat.rml",
settings = "apps/settings/settings.rml",
browser = "apps/browser/browser.rml",
store = "apps/store/store.rml",
camera = "apps/camera/camera.rml",
music = "apps/music/music.rml"
}
-- Use global state to persist across document loads
-- Initialize only if not already set
if not _G.nav_state then
_G.nav_state = {
history = {},
current_screen = "home",
nav_direction = "none" -- "forward", "back", "home", "none"
}
end
-- Local references for convenience
local history = _G.nav_state.history
local function get_current() return _G.nav_state.current_screen end
local function set_current(s) _G.nav_state.current_screen = s end
local function get_direction() return _G.nav_state.nav_direction end
local function set_direction(d) _G.nav_state.nav_direction = d end
-- Apply animation class based on navigation direction
local function applyNavAnimation()
local dir = get_direction()
print("Applying animation, direction: " .. dir)
if dir ~= "none" and document then
-- In RmlUi Lua, get body element
local body = document.body
if body then
print("Found body element, setting class nav-" .. dir)
-- Set the appropriate animation class
if dir == "forward" then
body:SetClass("nav-forward", true)
elseif dir == "back" then
body:SetClass("nav-back", true)
elseif dir == "home" then
body:SetClass("nav-home", true)
else
body:SetClass("nav-default", true)
end
else
print("Body element not found!")
end
end
end
-- Navigate to a screen by name
function navigateTo(screen_name)
print("navigateTo called with: " .. tostring(screen_name))
local path = screens[screen_name]
if path then
-- Push current screen to history before navigating
table.insert(history, get_current())
set_current(screen_name)
set_direction("forward")
-- Load the new screen using C++ function
local success = loadScreen(path)
if success then
applyNavAnimation()
print("Navigated to: " .. screen_name .. " (history depth: " .. #history .. ")")
else
-- Restore previous state on failure
set_current(table.remove(history))
print("Failed to navigate to: " .. screen_name)
end
return success
else
print("Unknown screen: " .. screen_name)
return false
end
end
-- Go back to previous screen
function goBack()
print("goBack called (history depth: " .. #history .. ")")
if #history > 0 then
local previous = table.remove(history)
local path = screens[previous]
if path then
set_current(previous)
set_direction("back")
loadScreen(path)
applyNavAnimation()
print("Back to: " .. previous)
return true
end
else
print("No history to go back to")
end
return false
end
-- Go to home screen (clear history)
function goHome()
-- Clear the history table
for i = #history, 1, -1 do
history[i] = nil
end
set_current("home")
set_direction("home")
loadScreen(screens.home)
applyNavAnimation()
print("Navigated to home")
end
-- Get current screen name
function getCurrentScreen()
return get_current()
end
-- Check if we can go back
function canGoBack()
return #history > 0
end
-- Clear navigation history
function clearHistory()
for i = #history, 1, -1 do
history[i] = nil
end
end
-- Get history depth
function getHistoryDepth()
return #history
end
print("Navigation system initialized (current: " .. get_current() .. ", history: " .. #history .. ")")

1485
base-apps/ui/components.rcss Normal file

File diff suppressed because it is too large Load Diff

93
base-apps/ui/html.rcss Normal file
View File

@@ -0,0 +1,93 @@
body, div,
h1, h2, h3, h4,
h5, h6, p,
hr, pre,
tabset tabs
{
display: block;
}
h1
{
font-size: 2em;
margin: .67em 0;
}
h2
{
font-size: 1.5em;
margin: .75em 0;
}
h3
{
font-size: 1.17em;
margin: .83em 0;
}
h4, p
{
margin: 1.12em 0;
}
h5
{
font-size: .83em;
margin: 1.5em 0;
}
h6
{
font-size: .75em;
margin: 1.67em 0;
}
h1, h2, h3, h4,
h5, h6, strong
{
font-weight: bold;
}
em
{
font-style: italic;
}
pre
{
white-space: pre;
}
hr
{
border-width: 1px;
}
table
{
box-sizing: border-box;
display: table;
}
tr
{
box-sizing: border-box;
display: table-row;
}
td
{
box-sizing: border-box;
display: table-cell;
}
col
{
box-sizing: border-box;
display: table-column;
}
colgroup
{
display: table-column-group;
}
thead, tbody, tfoot
{
display: table-row-group;
}

270
base-apps/ui/layout.rcss Normal file
View File

@@ -0,0 +1,270 @@
/* ==============================================
Layout Components: Reusable App Layout Structure
System status bar, app bar, navigation bar
============================================== */
/* ============== System Status Bar ============== */
/* Top bar showing time, signal, wifi, battery */
.system-status-bar {
height: 36px;
padding: 0 16px;
background-color: transparent;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #FFFFFF;
z-index: 1000;
}
.system-status-bar.bg-surface {
background-color: #1E1E1E;
}
.system-status-time {
font-weight: 500;
font-size: 16px;
}
.system-status-icons {
display: flex;
gap: 8px;
align-items: center;
}
.system-status-icons img {
width: 24px;
height: 24px;
pointer-events: none;
}
/* ============== App Bar ============== */
/* Title bar with back button and optional actions */
.app-bar {
height: 72px;
padding: 0 8px;
background-color: #1E1E1E;
display: flex;
align-items: center;
z-index: 900;
}
.app-bar.transparent {
background-color: transparent;
}
.app-bar.primary {
background-color: #121212;
}
.app-bar-back {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 28px;
}
.app-bar-back:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.app-bar-back:active {
background-color: rgba(255, 255, 255, 0.2);
}
.app-bar-back img {
width: 32px;
height: 32px;
pointer-events: none;
}
.app-bar-title {
flex: 1;
font-size: 24px;
font-weight: 500;
color: #FFFFFF;
padding-left: 8px;
}
.app-bar-actions {
display: flex;
gap: 4px;
}
.app-bar-action {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 28px;
}
.app-bar-action:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.app-bar-action:active {
background-color: rgba(255, 255, 255, 0.2);
}
.app-bar-action img {
width: 28px;
height: 28px;
pointer-events: none;
}
/* ============== System Navigation Bar ============== */
/* Bottom bar with back, home, and recent buttons */
.system-nav-bar {
height: 56px;
background-color: #0A0A0A;
display: flex;
align-items: center;
justify-content: space-around;
z-index: 1000;
}
.system-nav-bar.transparent {
background-color: rgba(10, 10, 10, 0.9);
}
.system-nav-btn {
width: 72px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 24px;
}
.system-nav-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.system-nav-btn:active {
background-color: rgba(255, 255, 255, 0.2);
}
.system-nav-btn img {
width: 28px;
height: 28px;
pointer-events: none;
opacity: 0.8;
}
/* Home button - pill shape */
.system-nav-home {
width: 96px;
height: 8px;
background-color: #FFFFFF;
border-radius: 4px;
cursor: pointer;
opacity: 0.6;
}
.system-nav-home:hover {
opacity: 0.8;
}
.system-nav-home:active {
opacity: 1.0;
}
/* ============== Screen Layout ============== */
/* Standard app screen structure */
.app-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.app-content {
flex: 1;
overflow: auto;
display: flex;
flex-direction: column;
}
/* Content padding for nav bar */
.app-content.with-nav {
padding-bottom: 56px;
}
/* Content padding for dock */
.app-content.with-dock {
padding-bottom: 100px;
}
/* ============== Combined Header ============== */
/* Status bar + App bar combined */
.app-header {
display: flex;
flex-direction: column;
background-color: #1E1E1E;
}
.app-header.transparent {
background-color: transparent;
}
.app-header .system-status-bar {
background-color: transparent;
}
/* ============== Notification Badge ============== */
.notification-badge {
position: absolute;
top: 8px;
right: 8px;
min-width: 20px;
height: 20px;
background-color: #CF6679;
border-radius: 10px;
font-size: 12px;
font-weight: 600;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
padding: 0 6px;
}
/* ============== Recording Indicator ============== */
/* Shown when camera/mic is active */
.recording-indicator {
position: absolute;
top: 8px;
right: 8px;
width: 12px;
height: 12px;
background-color: #F44336;
border-radius: 6px;
animation: recording-pulse 1.5s ease-in-out infinite;
}
@keyframes recording-pulse {
0%, 100% { opacity: 1.0; }
50% { opacity: 0.4; }
}
/* ============== Divider ============== */
.header-divider {
height: 1px;
background-color: #333333;
}

333
base-apps/ui/theme.rcss Normal file
View File

@@ -0,0 +1,333 @@
/* ==============================================
Theme: Material Dark for Virtual Smartphone (VR Optimized)
All sizes increased for VR readability and raycast interaction
============================================== */
/* Base body styling */
body {
font-family: LatoLatin;
background-color: #121212;
color: #FFFFFF;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
animation: 0.2s cubic-out fade-in;
}
/* ============== Typography (VR-sized) ============== */
.text-h1 {
font-size: 120px;
font-weight: 300;
}
.text-h2 {
font-size: 80px;
font-weight: 300;
}
.text-h3 {
font-size: 64px;
font-weight: 400;
}
.text-h4 {
font-size: 48px;
font-weight: 400;
}
.text-h5 {
font-size: 32px;
font-weight: 400;
}
.text-h6 {
font-size: 28px;
font-weight: 500;
}
.text-body1 {
font-size: 22px;
font-weight: 400;
}
.text-body2 {
font-size: 18px;
font-weight: 400;
}
.text-caption {
font-size: 16px;
font-weight: 400;
}
.text-overline {
font-size: 14px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 2px;
}
/* ============== Text Colors ============== */
.text-primary {
color: #FFFFFF;
}
.text-secondary {
color: #B3B3B3;
}
.text-disabled {
color: #666666;
}
.text-accent {
color: #BB86FC;
}
.text-accent-secondary {
color: #03DAC6;
}
.text-error {
color: #CF6679;
}
/* ============== Background Colors ============== */
.bg-primary {
background-color: #121212;
}
.bg-surface {
background-color: #1E1E1E;
}
.bg-surface-variant {
background-color: #2D2D2D;
}
.bg-accent {
background-color: #BB86FC;
}
.bg-accent-secondary {
background-color: #03DAC6;
}
.bg-error {
background-color: #CF6679;
}
/* Hover highlight color - used for interactive element feedback */
.bg-hover {
background-color: rgba(255, 255, 255, 0.12);
}
/* ============== Spacing Utilities (VR-scaled) ============== */
.p-0 { padding: 0; }
.p-1 { padding: 6px; }
.p-2 { padding: 12px; }
.p-3 { padding: 18px; }
.p-4 { padding: 24px; }
.p-5 { padding: 36px; }
.p-6 { padding: 48px; }
.p-8 { padding: 72px; }
.m-0 { margin: 0; }
.m-1 { margin: 6px; }
.m-2 { margin: 12px; }
.m-3 { margin: 18px; }
.m-4 { margin: 24px; }
.m-5 { margin: 36px; }
.m-6 { margin: 48px; }
.m-8 { margin: 72px; }
.mt-1 { margin-top: 6px; }
.mt-2 { margin-top: 12px; }
.mt-3 { margin-top: 18px; }
.mt-4 { margin-top: 24px; }
.mb-1 { margin-bottom: 6px; }
.mb-2 { margin-bottom: 12px; }
.mb-3 { margin-bottom: 18px; }
.mb-4 { margin-bottom: 24px; }
/* ============== Layout Utilities ============== */
.flex {
display: flex;
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-between {
display: flex;
justify-content: space-between;
}
.flex-around {
display: flex;
justify-content: space-around;
}
.flex-1 {
flex: 1;
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
/* ============== Border Utilities ============== */
.rounded {
border-radius: 6px;
}
.rounded-lg {
border-radius: 12px;
}
.rounded-xl {
border-radius: 20px;
}
.rounded-full {
border-radius: 9999px;
}
/* ============== Screen Structure ============== */
.screen {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #121212;
}
.screen-content {
flex: 1;
overflow: auto;
}
/* ============== Animations ============== */
@keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes slide-in-right {
0% { transform: translateX(100px); opacity: 0; }
100% { transform: translateX(0px); opacity: 1; }
}
@keyframes slide-in-left {
0% { transform: translateX(-100px); opacity: 0; }
100% { transform: translateX(0px); opacity: 1; }
}
@keyframes slide-up {
0% { transform: translateY(50px); opacity: 0; }
100% { transform: translateY(0px); opacity: 1; }
}
@keyframes scale-in {
0% { transform: scale(0.9); opacity: 0; }
100% { transform: scale(1.0); opacity: 1; }
}
/* Hover highlight animation */
@keyframes hover-pulse {
0% { background-color: rgba(255, 255, 255, 0.0); }
50% { background-color: rgba(255, 255, 255, 0.15); }
100% { background-color: rgba(255, 255, 255, 0.1); }
}
/* Screen transition classes */
.nav-forward {
animation: 0.2s cubic-out slide-in-right;
}
.nav-back {
animation: 0.2s cubic-out slide-in-left;
}
.nav-home {
animation: 0.2s back-out scale-in;
}
.nav-default {
animation: 0.15s cubic-out fade-in;
}
/* Animation utility classes */
.animate-fade-in {
animation: 0.2s cubic-out fade-in;
}
.animate-slide-right {
animation: 0.25s cubic-out slide-in-right;
}
.animate-slide-left {
animation: 0.25s cubic-out slide-in-left;
}
.animate-slide-up {
animation: 0.2s cubic-out slide-up;
}
.animate-scale-in {
animation: 0.2s back-out scale-in;
}
/* ============== Interactive Base Class ============== */
/* All interactive elements should use cursor: pointer and have hover feedback */
.interactive {
cursor: pointer;
}
.interactive:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.interactive:active {
background-color: rgba(255, 255, 255, 0.2);
}

View File

@@ -1097,7 +1097,7 @@ bool InitializeRmlUi(const std::string& assets_path, int fb_width, int fb_height
lua_pushstring(L, app.GetIconPath().c_str());
lua_setfield(L, -2, "icon");
lua_pushboolean(L, false); // Not a system app
lua_pushboolean(L, app.is_system_app);
lua_setfield(L, -2, "is_system_app");
lua_pushstring(L, app.app_path.c_str());

View File

@@ -68,6 +68,7 @@ bool AppDiscovery::LoadAppManifest(const std::string& app_directory, AppInfo& in
info.version = j.value("version", "1.0.0");
info.icon = j.value("icon", "icon.tga");
info.description = j.value("description", "");
info.is_system_app = j.value("is_system_app", false);
// Normalize path separators
info.app_path = app_directory;

View File

@@ -11,13 +11,20 @@ struct AppInfo {
std::string name; // Display name
std::string version; // Version string
std::string entry; // Entry point (e.g., "main.rml")
std::string icon; // Icon filename (e.g., "icon.tga")
std::string icon; // Icon filename (e.g., "icon.tga") or path (e.g., "/system/icons/phone.tga")
std::string description; // App description
std::string app_path; // Full path to app directory
bool is_system_app = false; // True for system apps (com.mosis.*)
// Computed paths
std::string GetEntryPath() const { return app_path + "/" + entry; }
std::string GetIconPath() const { return app_path + "/" + icon; }
std::string GetIconPath() const {
// If icon starts with /, it's an absolute/system path - don't prepend app_path
if (!icon.empty() && icon[0] == '/') {
return icon;
}
return app_path + "/" + icon;
}
};
class AppDiscovery {

386
docs/BASE_APPS.md Normal file
View File

@@ -0,0 +1,386 @@
# Base Apps Migration Plan
Convert system apps from embedded assets to standalone `.mosis` packages in `base-apps/`.
---
## Current State
### Apps in `src/main/assets/apps/`
| App | Files | Icon |
|-----|-------|------|
| home | home.rml, home.lua, lock.rml | home.tga |
| dialer | dialer.rml, calling.rml | phone.tga |
| contacts | contacts.rml | contacts.tga |
| messages | messages.rml | message.tga |
| settings | settings.rml | settings.tga |
| browser | browser.rml | browser.tga |
| camera | camera.rml | camera.tga |
| store | store.rml | store.tga |
| music | music.rml | music.tga |
### Shared Dependencies
All apps currently reference shared resources via relative paths:
```
../../ui/html.rcss - Base HTML element styles
../../ui/theme.rcss - Color scheme, typography
../../ui/components.rcss - Buttons, cards, lists
../../scripts/navigation.lua - Screen navigation system
../../icons/*.tga - App and UI icons
```
### Current Navigation Model
Apps are not truly separate - they're screens loaded via a central navigation system:
```lua
-- navigation.lua
local screens = {
home = "apps/home/home.rml",
dialer = "apps/dialer/dialer.rml",
-- etc.
}
mosis.loadDocument(screens[name])
```
---
## Target State
### Directory Structure
```
base-apps/
├── com.mosis.home/
│ ├── manifest.json
│ ├── main.rml
│ ├── home.lua
│ ├── lock.rml
│ ├── icon.tga
│ └── styles.rcss
├── com.mosis.dialer/
│ ├── manifest.json
│ ├── main.rml (dialer.rml)
│ ├── calling.rml
│ ├── icon.tga
│ └── styles.rcss
├── com.mosis.contacts/
├── com.mosis.messages/
├── com.mosis.settings/
├── com.mosis.browser/
├── com.mosis.camera/
├── com.mosis.store/
├── com.mosis.music/
└── package.bat
```
### Package IDs
| App | Package ID |
|-----|------------|
| Home | com.mosis.home |
| Dialer | com.mosis.dialer |
| Contacts | com.mosis.contacts |
| Messages | com.mosis.messages |
| Settings | com.mosis.settings |
| Browser | com.mosis.browser |
| Camera | com.mosis.camera |
| Store | com.mosis.store |
| Music | com.mosis.music |
---
## Key Decisions
### 1. Shared Resources Strategy
**Decision**: System asset path (`/system/`)
Apps reference shared resources via a system path that the kernel provides:
```html
<link type="text/rcss" href="/system/ui/theme.rcss"/>
<script src="/system/scripts/navigation.lua"></script>
```
The kernel maps `/system/` to `src/main/assets/` (or bundled assets on Android).
**Rationale**:
- Avoids duplicating ~50KB of styles/icons per app
- Single source of truth for theming
- Apps can still have local overrides
### 2. Navigation Model
**Decision**: Hybrid approach
- **System apps** (home, dialer, contacts, etc.) use shared navigation for seamless transitions
- **Third-party apps** launch via `mosis.apps.launch()` with isolated sandbox
System apps get `is_system_app = true` flag:
- Can access `/system/` resources
- Share navigation state
- Can load each other's screens directly
### 3. Home App Special Status
The home app is the launcher/shell:
- Always running in background
- Provides dock and app grid
- Launches other apps via `mosis.apps.launch(package_id)`
- Cannot be uninstalled
---
## Implementation Tasks
### Phase 1: Infrastructure
- [ ] **1.1** Rename `test-apps/` to `base-apps/`
- [ ] **1.2** Move `com.mosis.sandbox-test` to `base-apps/`
- [ ] **1.3** Implement `/system/` path mapping in kernel
- Desktop: Map to `assets/` directory
- Android: Map to bundled assets
- [ ] **1.4** Add `is_system_app` flag to manifest schema
- [ ] **1.5** Update designer `--test-apps` flag to `--apps` for consistency
### Phase 2: Convert Apps
For each app:
- [ ] **2.1** Create package directory in `base-apps/com.mosis.{name}/`
- [ ] **2.2** Create `manifest.json` with proper metadata
- [ ] **2.3** Copy RML files, rename entry to `main.rml`
- [ ] **2.4** Copy app-specific Lua scripts
- [ ] **2.5** Copy icon from `icons/{name}.tga` to `icon.tga`
- [ ] **2.6** Update resource paths to use `/system/` prefix
- [ ] **2.7** Create `styles.rcss` for app-specific styles (extract from inline)
### Phase 3: Update Home App
- [ ] **3.1** Update home.lua to use `mosis.apps.launch()` for launching
- [ ] **3.2** Keep shared navigation for system app transitions
- [ ] **3.3** Display all installed apps (base + third-party) in grid
- [ ] **3.4** Update dock icons to launch via package ID
### Phase 4: Cleanup
- [ ] **4.1** Remove old `src/main/assets/apps/` directory
- [ ] **4.2** Update build scripts to package base-apps
- [ ] **4.3** Update Android to include base-apps packages
- [ ] **4.4** Update documentation
---
## Manifest Examples
### System App (Dialer)
```json
{
"id": "com.mosis.dialer",
"name": "Phone",
"version": "1.0.0",
"version_code": 1,
"entry": "main.rml",
"icon": "icon.tga",
"description": "Make and receive phone calls",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"permissions": [
"contacts.read"
],
"is_system_app": true,
"min_api_version": 1
}
```
### Home App (Launcher)
```json
{
"id": "com.mosis.home",
"name": "Home",
"version": "1.0.0",
"version_code": 1,
"entry": "main.rml",
"icon": "icon.tga",
"description": "Mosis Home Launcher",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"permissions": [
"system.launcher"
],
"is_system_app": true,
"is_launcher": true,
"min_api_version": 1
}
```
---
## Resource Path Mapping
### Before (Relative)
```html
<link type="text/rcss" href="../../ui/theme.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<img src="../../icons/phone.tga"/>
```
### After (System Path)
```html
<link type="text/rcss" href="/system/ui/theme.rcss"/>
<script src="/system/scripts/navigation.lua"></script>
<img src="/system/icons/phone.tga"/>
```
---
## Testing Checklist
- [ ] Designer loads home from `base-apps/com.mosis.home/`
- [ ] All base apps appear in home screen grid
- [ ] Tapping app icon launches the app
- [ ] Navigation between system apps works (back button, dock)
- [ ] Third-party apps (sandbox-test) launch in isolated sandbox
- [ ] `/system/` paths resolve correctly
- [ ] Android build includes all base-apps packages
- [ ] Hot-reload still works for development
---
## Open Questions
1. **Lock screen**: Part of home app or separate `com.mosis.lockscreen`?
2. **Calling screen**: Part of dialer or system-level overlay?
3. **Notifications**: System-level or per-app handling?
4. **Settings persistence**: Shared settings service or per-app?
---
## File Changes Summary
| Action | Path |
|--------|------|
| Rename | `test-apps/``base-apps/` |
| Create | `base-apps/com.mosis.{home,dialer,...}/` |
| Create | Each app's `manifest.json` |
| Move | App RML/Lua files to package dirs |
| Update | RML paths from `../../` to `/system/` |
| Delete | `src/main/assets/apps/` (after migration) |
| Update | Designer command line args |
| Update | Android asset bundling |
---
## Reusable Layout Components
New standardized UI components for consistent app layouts. Located in `src/main/assets/ui/layout.rcss` and `src/main/assets/scripts/layout.lua`.
### Components
| Component | CSS Class | Description |
|-----------|-----------|-------------|
| Status Bar | `.system-status-bar` | Top bar with time, wifi, signal, battery |
| App Bar | `.app-bar` | Title bar with back button and actions |
| System Nav | `.system-nav-bar` | Bottom bar with back, home, recent buttons |
| App Screen | `.app-screen` | Standard app screen container |
| App Content | `.app-content` | Scrollable content area |
### Standard App Structure
```html
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">App Title</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Content Area -->
<div class="app-content with-nav">
<!-- App content here -->
</div>
<!-- System Navigation Bar -->
<div class="system-nav-bar">
<div class="system-nav-btn" onclick="onBackPressed()">
<img src="../../icons/back.tga"/>
</div>
<div class="system-nav-home" onclick="onHomePressed()"></div>
<div class="system-nav-btn" onclick="onRecentPressed()">
<img src="../../icons/menu.tga"/>
</div>
</div>
</body>
```
### Required Includes
```html
<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"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
</head>
```
### CSS Modifiers
| Class | Description |
|-------|-------------|
| `.app-content.with-nav` | Adds bottom padding for system nav bar |
| `.app-content.with-dock` | Adds bottom padding for dock |
| `.app-bar.transparent` | Transparent app bar background |
| `.system-status-bar.bg-surface` | Solid surface color background |
### Lua Functions (layout.lua)
| Function | Description |
|----------|-------------|
| `initLayout(doc)` | Initialize all layout components |
| `initStatusBar(doc)` | Setup status bar time updates |
| `onBackPressed()` | Handle back button (goes back or home) |
| `onHomePressed()` | Handle home button |
| `onRecentPressed()` | Handle recent apps button |
### Updated Apps
- [x] Settings - Uses full layout with system nav bar
- [x] Contacts - Uses status bar and app bar (has own bottom tabs)
- [ ] Other apps - Still using old structure
---
*Created: 2025-01-19*
*Updated: 2025-01-20*

View File

@@ -1,26 +1,239 @@
# Automated Testing Framework
The designer-test (`designer-test/`) provides automated UI testing.
The Mosis Designer supports two testing approaches:
1. **JSON Action Playback** - Built into the designer for scripted UI testing
2. **C++ Test Framework** - External test runner in `designer-test/` for programmatic testing
## Test Architecture
---
1. **WindowController**: Finds designer window, sends mouse/keyboard events via Windows SendInput API
2. **HierarchyReader**: Parses UI hierarchy JSON to find elements by ID/class (with retry logic and exponential backoff)
3. **LogParser**: Monitors log file for navigation events
4. **TestRunner**: Orchestrates test execution, reports results
5. **UIInspector**: Dumps UI hierarchy with atomic writes (temp file + rename pattern)
## JSON Action Playback
## Key Implementation Details
The designer can record and playback UI interactions using JSON files. This is useful for:
- Automated screenshot capture
- Regression testing
- Demo recordings
- UI verification
- **Path Normalization**: RmlUi uses `|` instead of `:` in Windows paths (e.g., `D|\Dev\...`). The UIInspector normalizes paths for correct document matching.
- **Atomic File Writes**: Hierarchy files are written to `.tmp` then renamed to prevent partial reads.
- **Retry with Backoff**: HierarchyReader retries up to 10 times with exponential backoff (30ms base) and validates JSON completeness.
- **Dynamic Back Button**: `GoHome()` finds back buttons from hierarchy by class (`app-bar-nav` or `browser-nav-btn`) instead of fixed coordinates.
### Command-Line Options
## Writing Tests
```bash
# Run with action playback
mosis-designer.exe --simulator --test-apps base-apps \
--playback test_navigation.json \
--screenshot-after result.png \
--hierarchy hierarchy.json
```
| Flag | Description |
|------|-------------|
| `--playback <file>` | JSON file containing actions to replay |
| `--screenshot-after <file>` | Save screenshot after playback completes |
| `--hierarchy <file>` | Dump UI hierarchy to JSON after playback |
### Recording Actions
Press `R` in the designer to start/stop recording. Actions are saved to `recorded_actions.json`.
### Action Types
#### Tap
Single tap at coordinates.
```json
{
"type": "tap",
"x": 270,
"y": 480,
"timestamp": 1000
}
```
#### Swipe
Drag from one point to another.
```json
{
"type": "swipe",
"x1": 270,
"y1": 600,
"x2": 270,
"y2": 200,
"duration": 300,
"timestamp": 2000
}
```
#### Long Press
Press and hold at coordinates.
```json
{
"type": "long_press",
"x": 270,
"y": 480,
"duration": 800,
"timestamp": 3000
}
```
#### Button
System button press (back, home, recents).
```json
{
"type": "button",
"button": "back",
"timestamp": 4000
}
```
#### Wait
Pause execution for a duration.
```json
{
"type": "wait",
"duration": 1500,
"timestamp": 5000
}
```
#### Key
Raw keyboard input (for special keys).
```json
{
"type": "key",
"key_code": 256,
"pressed": true,
"timestamp": 6000
}
```
Common key codes:
- `256` - Escape (Back)
- `301` - F12 (Debugger)
- `294` - F5 (Reload)
### Complete Test File Example
```json
{
"name": "Navigate to Messages",
"description": "Test navigation from home to Messages app",
"screen_width": 540,
"screen_height": 960,
"initial_screen": "",
"actions": [
{
"type": "wait",
"duration": 500,
"timestamp": 0
},
{
"type": "tap",
"x": 207,
"y": 220,
"timestamp": 500
},
{
"type": "wait",
"duration": 1500,
"timestamp": 600
}
]
}
```
### Finding Tap Coordinates
Use the `--hierarchy` flag to dump UI element bounds:
```bash
mosis-designer.exe --simulator --test-apps base-apps \
--playback wait_only.json \
--hierarchy hierarchy.json
```
The hierarchy JSON contains element bounds:
```json
{
"bounds": {
"x": 171.5,
"y": 183.2,
"width": 72.0,
"height": 72.0
},
"classes": ["app-icon-image"],
"tag": "div"
}
```
Calculate tap center: `x = bounds.x + width/2`, `y = bounds.y + height/2`
### Home Screen Grid Layout
For the default 540x960 resolution with 4-column grid:
| Row | Y Range | Center Y |
|-----|---------|----------|
| 1 | 64-167 | ~115 |
| 2 | 183-286 | ~220 |
| 3 | 302-405 | ~350 |
| Column | X Range | Center X |
|--------|---------|----------|
| 1 | 20-145 | ~82 |
| 2 | 145-270 | ~207 |
| 3 | 270-395 | ~332 |
| 4 | 395-520 | ~457 |
Dock items are at Y ~910.
---
## Asset Path Requirements
When testing apps in `base-apps/`, ensure shared assets are accessible. Apps use relative paths like `../../ui/html.rcss`.
From `base-apps/com.mosis.app/`, the path `../../` resolves to `MosisService/`.
Required directories at `MosisService/` root:
```
MosisService/
├── ui/ # html.rcss, theme.rcss, components.rcss, layout.rcss
├── scripts/ # navigation.lua, layout.lua
├── icons/ # Shared icon assets
└── base-apps/ # Test apps
```
Copy from `src/main/assets/`:
```bash
cp -r src/main/assets/ui .
cp -r src/main/assets/scripts .
cp -r src/main/assets/icons .
```
---
## C++ Test Framework
The `designer-test/` project provides programmatic testing with element finding.
### Architecture
| Component | Purpose |
|-----------|---------|
| WindowController | Sends mouse/keyboard via Windows SendInput API |
| HierarchyReader | Parses UI hierarchy JSON, finds elements by ID/class |
| LogParser | Monitors log file for navigation events |
| TestRunner | Orchestrates execution, reports results |
| UIInspector | Dumps hierarchy with atomic writes |
### Key Implementation Details
- **Path Normalization**: RmlUi uses `|` instead of `:` in Windows paths
- **Atomic File Writes**: Hierarchy written to `.tmp` then renamed
- **Retry with Backoff**: HierarchyReader retries up to 10 times with exponential backoff
- **Dynamic Back Button**: Finds back buttons by class instead of fixed coordinates
### Writing C++ Tests
```cpp
// Find element by ID and click it
bool ClickById(TestContext& ctx, const std::string& id) {
ctx.hierarchy.Reload();
auto element = ctx.hierarchy.FindById(id);
@@ -28,12 +241,11 @@ bool ClickById(TestContext& ctx, const std::string& id) {
int x = element->bounds.centerX();
int y = element->bounds.centerY();
ScaleToPhysical(ctx, x, y); // Convert logical to physical coords
ScaleToPhysical(ctx, x, y);
ctx.window.SendClick(x, y);
return true;
}
// Test example
bool TestNavigateToDialer(TestContext& ctx) {
GoHome(ctx);
ctx.log.Clear();
@@ -46,9 +258,9 @@ bool TestNavigateToDialer(TestContext& ctx) {
}
```
## Test Output
### Test Output
Tests produce JSON results at `test_results.json`:
Results saved to `test_results.json`:
```json
{
"name": "Mosis Designer UI Tests",
@@ -58,3 +270,91 @@ Tests produce JSON results at `test_results.json`:
]
}
```
---
## Example Test Suite
Test files for common scenarios:
### test_home_only.json
```json
{
"name": "Home Screenshot",
"description": "Capture home screen",
"screen_width": 540,
"screen_height": 960,
"actions": [
{"type": "wait", "duration": 500, "timestamp": 0}
]
}
```
### test_settings.json
```json
{
"name": "Navigate to Settings",
"description": "Tap Settings app (Row 3, Col 1)",
"screen_width": 540,
"screen_height": 960,
"actions": [
{"type": "wait", "duration": 500, "timestamp": 0},
{"type": "tap", "x": 82, "y": 350, "timestamp": 500},
{"type": "wait", "duration": 1500, "timestamp": 600}
]
}
```
### test_browser.json
```json
{
"name": "Navigate to Browser",
"description": "Tap Browser in dock (Col 4)",
"screen_width": 540,
"screen_height": 960,
"actions": [
{"type": "wait", "duration": 500, "timestamp": 0},
{"type": "tap", "x": 445, "y": 910, "timestamp": 500},
{"type": "wait", "duration": 1500, "timestamp": 600}
]
}
```
### Running Tests
```bash
# Single test with screenshot
mosis-designer.exe --simulator --test-apps base-apps \
--playback test_settings.json \
--screenshot-after screenshot_settings.png
# Test with hierarchy dump for debugging
mosis-designer.exe --simulator --test-apps base-apps \
--playback test_home_only.json \
--screenshot-after home.png \
--hierarchy hierarchy.json
```
---
## Troubleshooting
### Tap not registering
- Check coordinates against hierarchy bounds
- Ensure tap is within the clickable element (e.g., `app-icon-image`, not `app-icon-label`)
- Use hierarchy dump to verify element positions
### Stylesheet errors
```
[ERROR] Failed to load style sheet D|/Dev/.../ui/html.rcss
```
- Copy shared assets to MosisService root (see Asset Path Requirements)
### App not loading
- Check console for `launchApp` call
- Verify manifest.json exists in app directory
- Check entry file path in manifest
### Screenshot shows wrong screen
- Increase wait duration after tap
- Verify navigation completed in console output

View File

@@ -3,17 +3,11 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Browser</title>
<style>
.browser-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.browser-toolbar {
display: flex;
align-items: center;
@@ -23,48 +17,46 @@
}
.browser-nav-btn {
width: 56px;
height: 56px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #B3B3B3;
cursor: pointer;
border-radius: 28px;
border-radius: 24px;
}
.browser-nav-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.browser-nav-btn.disabled {
color: #444444;
cursor: default;
.browser-nav-btn img {
width: 28px;
height: 28px;
}
.browser-nav-btn.disabled:hover {
background-color: transparent;
.browser-nav-btn.disabled img {
opacity: 0.3;
}
.browser-url-bar {
flex: 1;
display: flex;
align-items: center;
padding: 8px 16px;
padding: 10px 16px;
background-color: #2D2D2D;
border-radius: 20px;
}
.browser-secure-icon {
font-size: 18px;
font-size: 16px;
color: #4CAF50;
margin-right: 8px;
}
.browser-url {
flex: 1;
font-size: 18px;
font-size: 16px;
color: #FFFFFF;
background: transparent;
border: none;
@@ -89,7 +81,7 @@
}
.browser-page-text {
font-size: 18px;
font-size: 16px;
line-height: 1.6;
color: #333333;
margin-bottom: 16px;
@@ -104,13 +96,11 @@
text-decoration: underline;
}
.browser-search-results {
padding: 16px;
}
.browser-search-item {
margin-bottom: 24px;
margin-bottom: 20px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
}
.browser-search-item:hover {
@@ -124,13 +114,13 @@
}
.browser-search-url {
font-size: 16px;
font-size: 14px;
color: #006621;
margin-bottom: 4px;
}
.browser-search-desc {
font-size: 18px;
font-size: 16px;
color: #545454;
line-height: 1.4;
}
@@ -157,77 +147,78 @@
color: #FFFFFF;
}
.browser-tab-icon {
font-size: 20px;
.browser-tab-btn img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.browser-tab-label {
font-size: 16px;
.browser-tab-btn span {
font-size: 14px;
}
.browser-tabs-indicator {
padding: 6px 10px;
border: 1px solid #B3B3B3;
border-radius: 6px;
font-size: 16px;
font-size: 14px;
color: #B3B3B3;
}
</style>
</head>
<body class="browser-screen" data-model="browser">
<!-- 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>
<body class="app-screen" onload="initLayout(document)" data-model="browser">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- Browser Toolbar -->
<div class="browser-toolbar">
<div class="app-bar-nav browser-nav-btn" data-class-disabled="!can_go_back" onclick="goBack()">
<img src="../../icons/back.tga" style="width: 32px; height: 32px;"/>
<div class="browser-nav-btn" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<div class="browser-nav-btn disabled">
<img src="../../icons/forward.tga" style="width: 32px; height: 32px; opacity: 0.3;"/>
<img src="../../icons/forward.tga"/>
</div>
<div class="browser-url-bar">
<span class="browser-secure-icon">🔒</span>
<input class="browser-url" type="text" data-value="current_url"/>
</div>
<div class="browser-nav-btn" data-event-click="refresh()">
<img src="../../icons/refresh.tga" style="width: 32px; height: 32px;"/>
<span class="browser-secure-icon">S</span>
<input class="browser-url" type="text" value="example.com"/>
</div>
<div class="browser-nav-btn">
<img src="../../icons/more.tga" style="width: 32px; height: 32px;"/>
<img src="../../icons/refresh.tga"/>
</div>
<div class="browser-nav-btn">
<img src="../../icons/more.tga"/>
</div>
</div>
<!-- Browser Content -->
<div class="browser-content">
<div class="browser-page">
<div class="browser-page-title">{{ page_title }}</div>
<div class="browser-page-title">Example Domain</div>
<div class="browser-page-text">
{{ page_content }}
This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
</div>
<div class="browser-page-text">
<span class="browser-page-link" data-event-click="navigate('iana.org')">More information...</span>
<span class="browser-page-link">More information...</span>
</div>
<div style="margin-top: 32px; padding-top: 16px; border-top: 1px solid #e0e0e0;">
<div class="browser-page-title" style="font-size: 18px;">Related Links</div>
<div class="browser-search-item" data-event-click="navigate('iana.org/domains')">
<div class="browser-search-title">IANA IANA-managed Reserved Domains</div>
<div class="browser-search-item">
<div class="browser-search-title">IANA - IANA-managed Reserved Domains</div>
<div class="browser-search-url">www.iana.org > domains > reserved</div>
<div class="browser-search-desc">Certain domains are set aside and unavailable for registration. Learn about reserved top-level domains.</div>
<div class="browser-search-desc">Certain domains are set aside and unavailable for registration.</div>
</div>
<div class="browser-search-item" data-event-click="navigate('tools.ietf.org/html/rfc2606')">
<div class="browser-search-item">
<div class="browser-search-title">RFC 2606 - Reserved Top Level DNS Names</div>
<div class="browser-search-url">tools.ietf.org > html > rfc2606</div>
<div class="browser-search-desc">This document describes some domain names that are reserved for documentation purposes.</div>
<div class="browser-search-desc">This document describes domain names reserved for documentation.</div>
</div>
</div>
</div>
@@ -236,20 +227,20 @@
<!-- Bottom Bar -->
<div class="browser-bottom-bar">
<div class="browser-tab-btn" onclick="goHome()">
<img src="../../icons/home.tga" class="browser-tab-icon" style="width: 32px; height: 32px;"/>
<span class="browser-tab-label">Home</span>
<img src="../../icons/home.tga"/>
<span>Home</span>
</div>
<div class="browser-tab-btn">
<span class="browser-tabs-indicator">{{ tabs.size }}</span>
<span class="browser-tab-label">Tabs</span>
<span class="browser-tabs-indicator">1</span>
<span>Tabs</span>
</div>
<div class="browser-tab-btn">
<img src="../../icons/add.tga" class="browser-tab-icon" style="width: 32px; height: 32px;"/>
<span class="browser-tab-label">New Tab</span>
<img src="../../icons/add.tga"/>
<span>New Tab</span>
</div>
<div class="browser-tab-btn">
<img src="../../icons/menu.tga" class="browser-tab-icon" style="width: 32px; height: 32px;"/>
<span class="browser-tab-label">Menu</span>
<img src="../../icons/menu.tga"/>
<span>Menu</span>
</div>
</div>
</body>

View File

@@ -3,15 +3,13 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Camera</title>
<style>
.camera-screen {
width: 100%;
height: 100%;
background-color: #000000;
display: flex;
flex-direction: column;
}
/* Top Controls */
@@ -21,16 +19,16 @@
align-items: center;
padding: 16px;
position: absolute;
top: 0;
top: 36px;
left: 0;
right: 0;
z-index: 10;
}
.camera-btn {
width: 56px;
height: 56px;
border-radius: 28px;
width: 48px;
height: 48px;
border-radius: 24px;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
@@ -43,8 +41,8 @@
}
.camera-btn img {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
}
/* Viewfinder */
@@ -66,7 +64,6 @@
position: relative;
}
/* Placeholder for camera feed - replace with shared texture */
.viewfinder-placeholder {
color: #666666;
font-size: 18px;
@@ -136,10 +133,10 @@
}
.camera-mode {
font-size: 18px;
font-size: 16px;
color: #B3B3B3;
cursor: pointer;
padding: 10px;
padding: 8px;
}
.camera-mode.active {
@@ -156,7 +153,7 @@
display: flex;
justify-content: space-around;
align-items: center;
padding: 24px 32px;
padding: 20px 32px;
background-color: rgba(0, 0, 0, 0.6);
}
@@ -173,8 +170,8 @@
}
.gallery-preview img {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
opacity: 0.7;
}
@@ -206,13 +203,6 @@
background-color: #FFFFFF;
}
.capture-btn.video .capture-btn-inner {
width: 28px;
height: 28px;
border-radius: 6px;
background-color: #F44336;
}
.switch-camera-btn {
width: 48px;
height: 48px;
@@ -229,51 +219,37 @@
}
.switch-camera-btn img {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
}
/* Settings Overlay */
.settings-value {
position: absolute;
bottom: 200px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.6);
padding: 10px 18px;
border-radius: 22px;
font-size: 18px;
color: #FFFFFF;
}
/* Flash modes */
/* Indicators */
.flash-indicator {
position: absolute;
top: 80px;
top: 100px;
left: 16px;
background-color: rgba(0, 0, 0, 0.4);
padding: 8px 14px;
border-radius: 14px;
font-size: 16px;
padding: 6px 12px;
border-radius: 12px;
font-size: 14px;
color: #FFFFFF;
}
/* Timer indicator */
.timer-indicator {
position: absolute;
top: 80px;
top: 100px;
right: 16px;
background-color: rgba(0, 0, 0, 0.4);
padding: 8px 14px;
border-radius: 14px;
font-size: 16px;
padding: 6px 12px;
border-radius: 12px;
font-size: 14px;
color: #FFFFFF;
}
/* Zoom slider */
/* Zoom control */
.zoom-control {
position: absolute;
bottom: 180px;
bottom: 200px;
left: 50%;
transform: translateX(-50%);
display: flex;
@@ -282,20 +258,20 @@
}
.zoom-btn {
width: 44px;
height: 44px;
border-radius: 22px;
width: 40px;
height: 40px;
border-radius: 20px;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
font-size: 20px;
color: #FFFFFF;
cursor: pointer;
}
.zoom-level {
font-size: 18px;
font-size: 16px;
color: #FFD700;
font-weight: 600;
min-width: 48px;
@@ -303,7 +279,17 @@
}
</style>
</head>
<body class="camera-screen">
<body class="app-screen camera-screen" onload="initLayout(document)">
<!-- System Status Bar (transparent) -->
<div class="system-status-bar" style="background-color: transparent; position: absolute; top: 0; left: 0; right: 0; z-index: 20;">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- Top Bar -->
<div class="camera-top-bar">
<div class="camera-btn" onclick="goBack()">
@@ -325,11 +311,10 @@
<!-- Viewfinder Area -->
<div class="viewfinder-container">
<div class="viewfinder" id="camera-viewfinder">
<!-- This is where the shared camera texture would be rendered -->
<div class="viewfinder-placeholder">
<div class="viewfinder-placeholder-icon">C</div>
<div>Camera Preview</div>
<div style="font-size: 16px; margin-top: 8px; color: #555555;">
<div style="font-size: 14px; margin-top: 8px; color: #555555;">
Tap to focus
</div>
</div>
@@ -346,10 +331,8 @@
<div class="focus-indicator"></div>
</div>
<!-- Flash Indicator -->
<!-- Indicators -->
<div class="flash-indicator">Flash: Auto</div>
<!-- Timer Indicator -->
<div class="timer-indicator">Timer: Off</div>
<!-- Zoom Control -->

View File

@@ -3,21 +3,14 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Contacts</title>
<style>
.contacts-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.contacts-list {
flex: 1;
overflow: auto;
padding-bottom: 56px;
}
.contact-letter {
@@ -41,6 +34,10 @@
background-color: rgba(255, 255, 255, 0.05);
}
.contact-item:active {
background-color: rgba(255, 255, 255, 0.1);
}
.contact-avatar {
width: 48px;
height: 48px;
@@ -58,7 +55,7 @@
}
.contact-name {
font-size: 16px;
font-size: 18px;
color: #FFFFFF;
}
@@ -82,35 +79,216 @@
background-color: rgba(76, 175, 80, 0.2);
}
.contact-call-btn:active {
background-color: rgba(76, 175, 80, 0.3);
}
.contact-call-btn img {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
pointer-events: none;
}
/* Phone app bottom tabs */
.phone-tabs {
height: 72px;
background-color: #1E1E1E;
display: flex;
}
.phone-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
color: #666666;
}
.phone-tab:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.phone-tab.active {
color: #BB86FC;
}
.phone-tab img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.phone-tab span {
font-size: 14px;
}
</style>
</head>
<body class="contacts-screen" data-model="contacts">
<body class="app-screen" onload="initLayout(document)" data-model="contacts">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()"><img src="../../icons/back.tga" style="width: 32px; height: 32px;"/></div>
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Contacts</span>
<div class="btn-icon"><img src="../../icons/add.tga" style="width: 32px; height: 32px;"/></div>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
<div class="app-bar-action">
<img src="../../icons/add.tga"/>
</div>
</div>
</div>
<!-- Search Bar -->
<div class="search-bar">
<img src="../../icons/search.tga" class="search-icon" style="width: 28px; height: 28px;"/>
<img src="../../icons/search.tga" class="search-icon" style="width: 24px; height: 24px;"/>
<input class="search-input" type="text" placeholder="Search contacts"/>
</div>
<!-- Contacts List -->
<div class="app-content">
<div class="contacts-list">
<div data-for="contact : contacts">
<div class="contact-item" data-event-click="select_contact(contact.id); navigateTo('contact_detail')">
<div class="contact-avatar" data-style-background-color="contact.color">{{ contact.initial }}</div>
<!-- A -->
<div class="contact-letter">A</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #E91E63;">A</div>
<div class="contact-info">
<div class="contact-name">{{ contact.name }}</div>
<div class="contact-phone">{{ contact.phone }}</div>
<div class="contact-name">Alice Johnson</div>
<div class="contact-phone">+1 555-0101</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #9C27B0;">A</div>
<div class="contact-info">
<div class="contact-name">Andrew Smith</div>
<div class="contact-phone">+1 555-0102</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- B -->
<div class="contact-letter">B</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #2196F3;">B</div>
<div class="contact-info">
<div class="contact-name">Bob Williams</div>
<div class="contact-phone">+1 555-0201</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- C -->
<div class="contact-letter">C</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #4CAF50;">C</div>
<div class="contact-info">
<div class="contact-name">Carol Davis</div>
<div class="contact-phone">+1 555-0301</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #FF9800;">C</div>
<div class="contact-info">
<div class="contact-name">Chris Miller</div>
<div class="contact-phone">+1 555-0302</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- D -->
<div class="contact-letter">D</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #F44336;">D</div>
<div class="contact-info">
<div class="contact-name">David Brown</div>
<div class="contact-phone">+1 555-0401</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- E -->
<div class="contact-letter">E</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #00BCD4;">E</div>
<div class="contact-info">
<div class="contact-name">Emma Wilson</div>
<div class="contact-phone">+1 555-0501</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- J -->
<div class="contact-letter">J</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #673AB7;">J</div>
<div class="contact-info">
<div class="contact-name">John Doe</div>
<div class="contact-phone">+1 555-1234</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- M -->
<div class="contact-letter">M</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #3F51B5;">M</div>
<div class="contact-info">
<div class="contact-name">Mary Taylor</div>
<div class="contact-phone">+1 555-0601</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #009688;">M</div>
<div class="contact-info">
<div class="contact-name">Michael Lee</div>
<div class="contact-phone">+1 555-0602</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
</div>
</div>
<!-- S -->
<div class="contact-letter">S</div>
<div class="contact-item">
<div class="contact-avatar" style="background-color: #795548;">S</div>
<div class="contact-info">
<div class="contact-name">Sarah Anderson</div>
<div class="contact-phone">+1 555-0701</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
@@ -120,21 +298,23 @@
</div>
<!-- FAB -->
<div class="btn-fab"><img src="../../icons/add.tga" style="width: 32px; height: 32px;"/></div>
<div class="btn-fab">
<img src="../../icons/add.tga" style="width: 32px; height: 32px;"/>
</div>
<!-- Bottom Navigation -->
<div class="bottom-nav">
<div class="bottom-nav-item" onclick="navigateTo('dialer')">
<img src="../../icons/dialpad.tga" class="bottom-nav-icon" style="width: 32px; height: 32px;"/>
<span class="bottom-nav-label">Keypad</span>
<!-- Phone App Bottom Tabs -->
<div class="phone-tabs">
<div class="phone-tab" onclick="navigateTo('dialer')">
<img src="../../icons/dialpad.tga"/>
<span>Keypad</span>
</div>
<div class="bottom-nav-item">
<img src="../../icons/history.tga" class="bottom-nav-icon" style="width: 32px; height: 32px;"/>
<span class="bottom-nav-label">Recent</span>
<div class="phone-tab">
<img src="../../icons/history.tga"/>
<span>Recent</span>
</div>
<div class="bottom-nav-item active">
<img src="../../icons/contacts.tga" class="bottom-nav-icon" style="width: 32px; height: 32px;"/>
<span class="bottom-nav-label">Contacts</span>
<div class="phone-tab active">
<img src="../../icons/contacts.tga"/>
<span>Contacts</span>
</div>
</div>
</body>

View File

@@ -3,126 +3,74 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Phone</title>
<style>
.dialer-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.dialer-tabs {
display: flex;
background-color: #1E1E1E;
border-bottom: 1px solid #333333;
}
.dialer-tab {
flex: 1;
padding: 16px;
text-align: center;
font-size: 18px;
color: #B3B3B3;
cursor: pointer;
}
.dialer-tab.active {
color: #BB86FC;
border-bottom: 2px solid #BB86FC;
}
.dialer-content {
flex: 1;
display: flex;
flex-direction: column;
}
.recent-calls {
/* Phone app bottom tabs */
.phone-tabs {
height: 72px;
background-color: #1E1E1E;
display: flex;
}
.phone-tab {
flex: 1;
overflow: auto;
}
.call-item {
display: flex;
align-items: center;
padding: 12px 16px;
}
.call-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.call-avatar {
width: 48px;
height: 48px;
border-radius: 24px;
background-color: #BB86FC;
margin-right: 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 20px;
color: #000000;
}
.call-info {
flex: 1;
}
.call-name {
font-size: 16px;
color: #FFFFFF;
}
.call-type {
font-size: 16px;
color: #B3B3B3;
margin-top: 2px;
}
.call-type.missed {
color: #CF6679;
}
.call-time {
font-size: 16px;
cursor: pointer;
color: #666666;
}
.call-action {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #4CAF50;
cursor: pointer;
border-radius: 28px;
.phone-tab:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.call-action:hover {
background-color: rgba(76, 175, 80, 0.2);
.phone-tab.active {
color: #BB86FC;
}
.phone-tab img {
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.phone-tab span {
font-size: 14px;
}
</style>
</head>
<body class="dialer-screen" data-model="phone">
<body class="app-screen" onload="initLayout(document)" data-model="phone">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()"><img src="../../icons/back.tga" style="width: 32px; height: 32px;"/></div>
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Phone</span>
</div>
<!-- Tabs -->
<div class="dialer-tabs">
<div class="dialer-tab active" id="tab-keypad">Keypad</div>
<div class="dialer-tab" id="tab-recent">Recent</div>
<div class="dialer-tab" onclick="navigateTo('contacts')">Contacts</div>
</div>
<!-- Dialer Content -->
<div class="dialer-content">
<!-- Dial Display -->
<div class="dial-display">{{ dial_number }}</div>
@@ -181,23 +129,28 @@
<!-- Call Actions -->
<div class="dial-actions">
<div style="width: 56px;"></div>
<div class="dial-call-btn" data-event-click="make_call()"><img src="../../icons/call_small.tga" style="width: 32px; height: 32px; pointer-events: none;"/></div>
<div class="btn-icon" data-event-click="dial_backspace()" style="width: 56px; height: 56px;"><img src="../../icons/backspace.tga" style="width: 32px; height: 32px; pointer-events: none;"/></div>
<div class="dial-call-btn" data-event-click="make_call()">
<img src="../../icons/call_small.tga" style="width: 32px; height: 32px; pointer-events: none;"/>
</div>
<div class="btn-icon" data-event-click="dial_backspace()" style="width: 56px; height: 56px;">
<img src="../../icons/backspace.tga" style="width: 32px; height: 32px; pointer-events: none;"/>
</div>
</div>
</div>
<!-- Bottom Navigation -->
<div class="bottom-nav">
<div class="bottom-nav-item active">
<img src="../../icons/dialpad.tga" class="bottom-nav-icon" style="width: 32px; height: 32px;"/>
<span class="bottom-nav-label">Keypad</span>
<!-- Phone App Bottom Tabs -->
<div class="phone-tabs">
<div class="phone-tab active">
<img src="../../icons/dialpad.tga"/>
<span>Keypad</span>
</div>
<div class="bottom-nav-item">
<img src="../../icons/history.tga" class="bottom-nav-icon" style="width: 32px; height: 32px;"/>
<span class="bottom-nav-label">Recent</span>
<div class="phone-tab">
<img src="../../icons/history.tga"/>
<span>Recent</span>
</div>
<div class="bottom-nav-item" onclick="navigateTo('contacts')">
<img src="../../icons/contacts.tga" class="bottom-nav-icon" style="width: 32px; height: 32px;"/>
<span class="bottom-nav-label">Contacts</span>
<div class="phone-tab" onclick="navigateTo('contacts')">
<img src="../../icons/contacts.tga"/>
<span>Contacts</span>
</div>
</div>
</body>

View File

@@ -34,27 +34,23 @@ function initHome(doc)
print("[Home] Initializing home screen...")
home_document = doc
-- Get installed third-party apps
-- Get all installed 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 = {}
-- Log each app
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 .. ")")
local app_type = app.is_system_app and "System" or "Third-party"
print("[Home] " .. app_type .. " 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()
-- Render all apps
renderInstalledApps()
end
-- Generate a color based on package_id
@@ -79,17 +75,17 @@ function getAppInitial(name)
return name:sub(1, 1):upper()
end
-- Render third-party apps into the grid
function renderThirdPartyApps()
-- Render all installed apps into the grid
function renderInstalledApps()
-- 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")
local grid = home_document:GetElementById("installed-apps")
if not grid then
print("[Home] third-party-apps container not found")
print("[Home] installed-apps container not found")
return
end
@@ -97,7 +93,7 @@ function renderThirdPartyApps()
grid.inner_rml = ""
if #installed_apps == 0 then
print("[Home] No third-party apps to display")
print("[Home] No apps to display")
return
end
@@ -110,25 +106,23 @@ function renderThirdPartyApps()
-- 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
local icon_path = app.icon
-- Handle /system/ paths - map to shared assets
if icon_path:sub(1, 8) == "/system/" then
-- Map /system/icons/foo.tga to ../../icons/foo.tga (relative to home)
icon_path = "../../" .. icon_path:sub(9) -- Remove "/system/" prefix
print("[Home] Mapped system icon: " .. app.icon .. " -> " .. icon_path)
elseif icon_path:sub(1, 1) ~= "/" and not icon_path:find(":/") then
-- Relative path - prepend install_path
if app.install_path and app.install_path ~= "" then
icon_path = app.install_path .. "/" .. icon_path
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)
icon_html = '<img src="' .. icon_path .. '" style="width: 48px; height: 48px;"/>'
print("[Home] Loading icon: " .. icon_path)
else
-- Fallback to initial letter
icon_html = '<span style="font-size: 28px; color: #000000;">' .. initial .. '</span>'
@@ -137,7 +131,7 @@ function renderThirdPartyApps()
html = html .. [[
<div class="app-icon">
<div class="app-icon-image" style="background-color: ]] .. color .. [[;"
onclick="launchThirdPartyApp(']] .. app.package_id .. [[')">
onclick="launchApp(']] .. app.package_id .. [[')">
]] .. icon_html .. [[
</div>
<span class="app-icon-label">]] .. app.name .. [[</span>
@@ -146,7 +140,7 @@ function renderThirdPartyApps()
end
grid.inner_rml = html
print("[Home] Rendered " .. #installed_apps .. " third-party apps")
print("[Home] Rendered " .. #installed_apps .. " apps")
end
-- Get app info by package_id
@@ -159,8 +153,8 @@ function getAppInfo(package_id)
return nil
end
-- Launch a third-party app
function launchThirdPartyApp(package_id)
-- Launch an app by package_id
function launchApp(package_id)
print("[Home] Launching app: " .. package_id)
if mosis and mosis.apps then

View File

@@ -44,12 +44,13 @@
}
/* Third-party apps use same sizing as system apps */
#third-party-apps .app-icon {
#installed-apps .app-icon {
width: 25%;
box-sizing: border-box;
padding: 8px 0;
}
#third-party-apps .app-icon-image {
#installed-apps .app-icon-image {
width: 72px;
height: 72px;
border-radius: 18px;
@@ -60,11 +61,11 @@
cursor: pointer;
}
#third-party-apps .app-icon-image:hover {
#installed-apps .app-icon-image:hover {
transform: scale(1.05);
}
#third-party-apps .app-icon-label {
#installed-apps .app-icon-label {
display: block;
text-align: center;
font-size: 16px;
@@ -86,80 +87,8 @@
<!-- 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">
<!-- All apps dynamically populated by home.lua -->
<div id="installed-apps" class="app-grid-section">
<!-- Apps will be rendered here by home.lua -->
</div>
</div>

View File

@@ -3,17 +3,11 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Messages</title>
<style>
.messages-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.conversations-list {
flex: 1;
overflow: auto;
@@ -30,15 +24,19 @@
background-color: rgba(255, 255, 255, 0.05);
}
.conversation-item:active {
background-color: rgba(255, 255, 255, 0.1);
}
.conversation-avatar {
width: 48px;
height: 48px;
border-radius: 24px;
width: 56px;
height: 56px;
border-radius: 28px;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-size: 22px;
color: #000000;
}
@@ -54,18 +52,18 @@
}
.conversation-name {
font-size: 16px;
font-size: 18px;
font-weight: 500;
color: #FFFFFF;
}
.conversation-time {
font-size: 16px;
font-size: 14px;
color: #666666;
}
.conversation-preview {
font-size: 18px;
font-size: 16px;
color: #B3B3B3;
margin-top: 4px;
white-space: nowrap;
@@ -88,30 +86,135 @@
}
</style>
</head>
<body class="messages-screen" data-model="messages">
<body class="app-screen" onload="initLayout(document)" data-model="messages">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()"><img src="../../icons/back.tga" style="width: 32px; height: 32px;"/></div>
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Messages</span>
<div class="btn-icon"><img src="../../icons/search.tga" style="width: 32px; height: 32px;"/></div>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Conversations List -->
<div class="app-content with-nav">
<div class="conversations-list">
<div class="conversation-item" data-for="conv : conversations" data-event-click="select_conversation(conv.id); navigateTo('chat')">
<div class="conversation-avatar" data-style-background-color="conv.color">{{ conv.name | slice(0, 1) }}</div>
<!-- Alice -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #E91E63;">A</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">{{ conv.name }}</span>
<span class="conversation-time">{{ conv.time }}</span>
<span class="conversation-name">Alice Johnson</span>
<span class="conversation-time">2:34 PM</span>
</div>
<div class="conversation-preview">Hey! Are you coming to the party tonight?</div>
</div>
<div class="conversation-unread">2</div>
</div>
<!-- Bob -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #2196F3;">B</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Bob Williams</span>
<span class="conversation-time">1:15 PM</span>
</div>
<div class="conversation-preview">Thanks for the help yesterday!</div>
</div>
</div>
<!-- Carol -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #4CAF50;">C</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Carol Davis</span>
<span class="conversation-time">Yesterday</span>
</div>
<div class="conversation-preview">The meeting has been rescheduled to Friday</div>
</div>
</div>
<!-- David -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #FF9800;">D</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">David Brown</span>
<span class="conversation-time">Yesterday</span>
</div>
<div class="conversation-preview">Can you send me the files?</div>
</div>
<div class="conversation-unread">1</div>
</div>
<!-- Emma -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #9C27B0;">E</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Emma Wilson</span>
<span class="conversation-time">Mon</span>
</div>
<div class="conversation-preview">See you at the coffee shop!</div>
</div>
</div>
<!-- Frank -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #00BCD4;">F</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Frank Miller</span>
<span class="conversation-time">Sun</span>
</div>
<div class="conversation-preview">Great game last night!</div>
</div>
</div>
<!-- Grace -->
<div class="conversation-item">
<div class="conversation-avatar" style="background-color: #673AB7;">G</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">Grace Lee</span>
<span class="conversation-time">Sat</span>
</div>
<div class="conversation-preview">Happy birthday! 🎂</div>
</div>
<div class="conversation-preview">{{ conv.last_message }}</div>
</div>
<div class="conversation-unread" data-if="conv.unread > 0">{{ conv.unread }}</div>
</div>
</div>
<!-- FAB -->
<div class="btn-fab"><img src="../../icons/add.tga" style="width: 32px; height: 32px;"/></div>
<div class="btn-fab">
<img src="../../icons/add.tga" style="width: 32px; height: 32px;"/>
</div>
<!-- System Navigation Bar -->
<div class="system-nav-bar">
<div class="system-nav-btn" onclick="onBackPressed()">
<img src="../../icons/back.tga"/>
</div>
<div class="system-nav-home" onclick="onHomePressed()"></div>
<div class="system-nav-btn" onclick="onRecentPressed()">
<img src="../../icons/menu.tga"/>
</div>
</div>
</body>
</rml>

View File

@@ -3,23 +3,16 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Music</title>
<style>
.music-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.music-content {
flex: 1;
overflow: auto;
}
/* Now Playing Mini Bar */
.mini-player {
display: flex;
align-items: center;
@@ -47,30 +40,25 @@
}
.mini-player-title {
font-size: 18px;
font-size: 16px;
color: #FFFFFF;
font-weight: 500;
}
.mini-player-artist {
font-size: 16px;
font-size: 14px;
color: #B3B3B3;
margin-top: 2px;
}
.mini-player-controls {
display: flex;
gap: 8px;
}
.mini-control-btn {
width: 56px;
height: 56px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 28px;
border-radius: 24px;
}
.mini-control-btn:hover {
@@ -78,26 +66,25 @@
}
.mini-control-btn img {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
}
/* Section Headers */
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 16px 12px 16px;
padding: 20px 16px 12px 16px;
}
.section-title {
font-size: 22px;
font-size: 20px;
font-weight: 700;
color: #FFFFFF;
}
.section-action {
font-size: 16px;
font-size: 14px;
font-weight: 600;
color: #B3B3B3;
cursor: pointer;
@@ -107,7 +94,6 @@
color: #FFFFFF;
}
/* Recently Played Row */
.recent-row {
display: flex;
overflow-x: auto;
@@ -116,35 +102,38 @@
}
.recent-item {
min-width: 130px;
min-width: 120px;
cursor: pointer;
}
.recent-item:hover {
opacity: 0.9;
}
.recent-art {
width: 130px;
height: 130px;
width: 120px;
height: 120px;
border-radius: 8px;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
font-size: 36px;
color: #FFFFFF;
}
.recent-title {
font-size: 18px;
font-size: 16px;
color: #FFFFFF;
font-weight: 500;
margin-bottom: 4px;
}
.recent-subtitle {
font-size: 16px;
font-size: 14px;
color: #B3B3B3;
}
/* Quick Access Cards */
.quick-access {
display: flex;
flex-wrap: wrap;
@@ -174,18 +163,17 @@
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-size: 18px;
color: #FFFFFF;
}
.quick-card-title {
font-size: 16px;
font-size: 14px;
font-weight: 600;
color: #FFFFFF;
padding: 0 12px;
}
/* Playlist Row */
.playlist-item {
display: flex;
align-items: center;
@@ -220,12 +208,11 @@
}
.playlist-meta {
font-size: 16px;
font-size: 14px;
color: #B3B3B3;
margin-top: 4px;
}
/* Bottom Navigation */
.music-bottom-nav {
display: flex;
height: 56px;
@@ -244,48 +231,62 @@
color: #B3B3B3;
}
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.nav-item.active {
color: #FFFFFF;
}
.nav-item img {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.nav-item span {
font-size: 16px;
font-size: 14px;
}
/* Color palette for album arts */
.bg-gradient-1 { background-color: #667eea; }
.bg-gradient-2 { background-color: #f093fb; }
.bg-gradient-3 { background-color: #4facfe; }
.bg-gradient-4 { background-color: #43e97b; }
.bg-gradient-5 { background-color: #fa709a; }
.bg-gradient-6 { background-color: #a8edea; }
.bg-solid-purple { background-color: #7c3aed; }
.bg-solid-red { background-color: #dc2626; }
.bg-solid-green { background-color: #16a34a; }
.bg-solid-blue { background-color: #2563eb; }
</style>
</head>
<body class="music-screen">
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()">
<img src="../../icons/back.tga" style="width: 32px; height: 32px;"/>
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Music</span>
<div class="btn-icon">
<img src="../../icons/search.tga" style="width: 32px; height: 32px;"/>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Content -->
<div class="music-content">
<!-- Good Morning Section -->
<!-- Good Afternoon Section -->
<div class="section-header">
<span class="section-title">Good afternoon</span>
</div>
@@ -376,14 +377,6 @@
<div class="playlist-meta">Your weekly mixtape</div>
</div>
</div>
<div class="playlist-item">
<div class="playlist-art bg-solid-green">R</div>
<div class="playlist-info">
<div class="playlist-title">Release Radar</div>
<div class="playlist-meta">New music from artists you follow</div>
</div>
</div>
</div>
<!-- Mini Player -->
@@ -393,7 +386,7 @@
<div class="mini-player-title">Midnight City</div>
<div class="mini-player-artist">M83</div>
</div>
<div class="mini-player-controls">
<div style="display: flex; gap: 4px;">
<div class="mini-control-btn">
<img src="../../icons/heart.tga"/>
</div>

View File

@@ -3,17 +3,11 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<title>Settings</title>
<style>
.settings-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.settings-list {
flex: 1;
overflow: auto;
@@ -24,7 +18,7 @@
.settings-section {
margin-bottom: 8px;
width: 540px;
width: 100%;
display: flex;
flex-direction: column;
}
@@ -37,10 +31,11 @@
}
.settings-item {
width: 540px;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
padding: 16px;
cursor: pointer;
background-color: #1E1E1E;
@@ -56,11 +51,18 @@
}
.settings-icon {
width: 56px;
height: 56px;
width: 48px;
height: 48px;
margin-right: 16px;
font-size: 28px;
color: #B3B3B3;
display: flex;
align-items: center;
justify-content: center;
}
.settings-icon img {
width: 32px;
height: 32px;
opacity: 0.7;
}
.settings-content {
@@ -68,7 +70,7 @@
}
.settings-title {
font-size: 16px;
font-size: 18px;
color: #FFFFFF;
}
@@ -81,6 +83,7 @@
.settings-action {
font-size: 20px;
color: #666666;
padding: 8px;
}
.settings-toggle {
@@ -111,13 +114,8 @@
left: 26px;
}
.settings-value {
font-size: 18px;
color: #B3B3B3;
}
.user-card {
width: 540px;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: row;
@@ -125,6 +123,11 @@
padding: 20px 16px;
background-color: #1E1E1E;
margin-bottom: 8px;
cursor: pointer;
}
.user-card:hover {
background-color: #252525;
}
.user-avatar {
@@ -133,10 +136,11 @@
border-radius: 32px;
background-color: #BB86FC;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
color: #000000;
text-align: center;
line-height: 64px;
}
.user-info {
@@ -150,21 +154,38 @@
}
.user-email {
font-size: 18px;
font-size: 16px;
color: #B3B3B3;
margin-top: 4px;
}
</style>
</head>
<body class="settings-screen">
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()"><img src="../../icons/back.tga" style="width: 32px; height: 32px;"/></div>
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Settings</span>
<div class="btn-icon"><img src="../../icons/search.tga" style="width: 32px; height: 32px;"/></div>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Settings List -->
<div class="app-content with-nav">
<div class="settings-list">
<!-- User Card -->
<div class="user-card">
@@ -180,7 +201,9 @@
<div class="settings-section">
<div class="settings-header">Network</div>
<div class="settings-item">
<div class="settings-icon">W</div>
<div class="settings-icon">
<img src="../../icons/wifi.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Wi-Fi</div>
<div class="settings-subtitle">Connected to MosisNetwork</div>
@@ -190,7 +213,9 @@
</div>
</div>
<div class="settings-item">
<div class="settings-icon">B</div>
<div class="settings-icon">
<img src="../../icons/signal.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Bluetooth</div>
<div class="settings-subtitle">Off</div>
@@ -200,7 +225,9 @@
</div>
</div>
<div class="settings-item">
<div class="settings-icon">A</div>
<div class="settings-icon">
<img src="../../icons/signal.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Airplane Mode</div>
</div>
@@ -214,7 +241,9 @@
<div class="settings-section">
<div class="settings-header">Device</div>
<div class="settings-item">
<div class="settings-icon">D</div>
<div class="settings-icon">
<img src="../../icons/settings.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Display</div>
<div class="settings-subtitle">Brightness, wallpaper, sleep</div>
@@ -222,7 +251,9 @@
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">S</div>
<div class="settings-icon">
<img src="../../icons/music.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Sound</div>
<div class="settings-subtitle">Volume, ringtone, vibration</div>
@@ -230,7 +261,9 @@
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">N</div>
<div class="settings-icon">
<img src="../../icons/message.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Notifications</div>
<div class="settings-subtitle">App notifications, Do not disturb</div>
@@ -238,7 +271,9 @@
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">B</div>
<div class="settings-icon">
<img src="../../icons/battery.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Battery</div>
<div class="settings-subtitle">85% - 4h 30m remaining</div>
@@ -246,7 +281,9 @@
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">S</div>
<div class="settings-icon">
<img src="../../icons/files.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Storage</div>
<div class="settings-subtitle">32 GB of 128 GB used</div>
@@ -259,7 +296,9 @@
<div class="settings-section">
<div class="settings-header">Privacy &amp; Security</div>
<div class="settings-item">
<div class="settings-icon">L</div>
<div class="settings-icon">
<img src="../../icons/account.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Lock Screen</div>
<div class="settings-subtitle">PIN, pattern, fingerprint</div>
@@ -267,7 +306,9 @@
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">P</div>
<div class="settings-icon">
<img src="../../icons/account.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Privacy</div>
<div class="settings-subtitle">Permissions, account activity</div>
@@ -275,7 +316,9 @@
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-icon">L</div>
<div class="settings-icon">
<img src="../../icons/maps.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Location</div>
<div class="settings-subtitle">On - High accuracy</div>
@@ -290,14 +333,28 @@
<div class="settings-section">
<div class="settings-header">About</div>
<div class="settings-item">
<div class="settings-icon">I</div>
<div class="settings-icon">
<img src="../../icons/phone.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">About Phone</div>
<div class="settings-subtitle">Model, software version</div>
<div class="settings-subtitle">Mosis Virtual Phone v1.0</div>
</div>
<span class="settings-action">></span>
</div>
</div>
</div>
</div>
<!-- System Navigation Bar -->
<div class="system-nav-bar">
<div class="system-nav-btn" onclick="onBackPressed()">
<img src="../../icons/back.tga"/>
</div>
<div class="system-nav-home" onclick="onHomePressed()"></div>
<div class="system-nav-btn" onclick="onRecentPressed()">
<img src="../../icons/menu.tga"/>
</div>
</div>
</body>
</rml>

View File

@@ -3,25 +3,18 @@
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="store.lua"></script>
<title>Store</title>
<style>
.store-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.store-content {
flex: 1;
overflow: auto;
padding-bottom: 16px;
}
/* Search Bar */
.store-search {
margin: 16px;
background-color: #2D2D2D;
@@ -29,11 +22,16 @@
padding: 12px 16px;
display: flex;
align-items: center;
cursor: pointer;
}
.store-search:hover {
background-color: #3D3D3D;
}
.store-search img {
width: 28px;
height: 28px;
width: 24px;
height: 24px;
margin-right: 12px;
opacity: 0.6;
}
@@ -43,7 +41,6 @@
color: #B3B3B3;
}
/* Section Headers */
.section-header {
display: flex;
align-items: center;
@@ -58,43 +55,46 @@
}
.section-action {
font-size: 18px;
font-size: 16px;
color: #BB86FC;
cursor: pointer;
}
/* Featured Banner */
.featured-banner {
margin: 0 16px 16px 16px;
height: 160px;
height: 140px;
background-color: #7C3AED;
border-radius: 16px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: flex-end;
cursor: pointer;
}
.featured-banner:hover {
opacity: 0.95;
}
.featured-tag {
font-size: 16px;
font-size: 14px;
color: rgba(255,255,255,0.7);
text-transform: uppercase;
margin-bottom: 8px;
}
.featured-title {
font-size: 24px;
font-size: 22px;
font-weight: 600;
color: #FFFFFF;
margin-bottom: 4px;
}
.featured-subtitle {
font-size: 18px;
font-size: 16px;
color: rgba(255,255,255,0.8);
}
/* App Cards Row */
.app-cards-row {
display: flex;
overflow-x: auto;
@@ -103,7 +103,7 @@
}
.app-card {
min-width: 140px;
min-width: 130px;
background-color: #1E1E1E;
border-radius: 12px;
padding: 12px;
@@ -115,44 +115,35 @@
}
.app-card-icon {
width: 64px;
height: 64px;
width: 56px;
height: 56px;
border-radius: 14px;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
font-size: 24px;
color: #000000;
}
.app-card-name {
font-size: 18px;
font-size: 16px;
font-weight: 500;
color: #FFFFFF;
margin-bottom: 4px;
}
.app-card-category {
font-size: 16px;
font-size: 14px;
color: #B3B3B3;
margin-bottom: 8px;
margin-bottom: 6px;
}
.app-card-rating {
display: flex;
align-items: center;
font-size: 16px;
font-size: 14px;
color: #B3B3B3;
}
.app-card-rating img {
width: 16px;
height: 16px;
margin-right: 4px;
}
/* App List Items */
.app-list-item {
display: flex;
align-items: center;
@@ -187,35 +178,18 @@
}
.app-list-meta {
font-size: 16px;
font-size: 14px;
color: #B3B3B3;
margin-top: 4px;
}
.app-list-rating {
display: flex;
align-items: center;
margin-top: 4px;
}
.app-list-rating img {
width: 18px;
height: 18px;
margin-right: 4px;
}
.app-list-rating span {
font-size: 16px;
color: #B3B3B3;
}
.install-btn {
background-color: #BB86FC;
color: #000000;
font-size: 16px;
font-size: 14px;
font-weight: 600;
padding: 10px 22px;
border-radius: 22px;
padding: 10px 20px;
border-radius: 20px;
cursor: pointer;
}
@@ -226,11 +200,8 @@
.install-btn.installed {
background-color: transparent;
color: #BB86FC;
border-width: 1px;
border-color: #BB86FC;
}
/* Category Chips */
.category-chips {
display: flex;
overflow-x: auto;
@@ -258,7 +229,6 @@
color: #000000;
}
/* Bottom Nav */
.store-bottom-nav {
display: flex;
height: 56px;
@@ -275,21 +245,24 @@
color: #B3B3B3;
}
.store-nav-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.store-nav-item.active {
color: #BB86FC;
}
.store-nav-item img {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
margin-bottom: 4px;
}
.store-nav-item span {
font-size: 16px;
font-size: 14px;
}
/* Color palette for app icons */
.bg-purple { background-color: #BB86FC; }
.bg-teal { background-color: #03DAC6; }
.bg-orange { background-color: #FF9800; }
@@ -298,131 +271,29 @@
.bg-red { background-color: #F44336; }
.bg-pink { background-color: #E91E63; }
.bg-indigo { background-color: #3F51B5; }
/* Dialog Overlay */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog {
background-color: #2D2D2D;
border-radius: 16px;
padding: 24px;
min-width: 280px;
max-width: 320px;
}
.dialog-title {
font-size: 20px;
font-weight: 600;
color: #FFFFFF;
margin-bottom: 16px;
}
.dialog-message {
font-size: 16px;
color: #B3B3B3;
margin-bottom: 24px;
line-height: 1.4;
}
.dialog-status {
font-size: 14px;
color: #B3B3B3;
margin-top: 12px;
text-align: center;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.dialog-btn {
background-color: transparent;
color: #BB86FC;
font-size: 16px;
font-weight: 600;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
}
.dialog-btn:hover {
background-color: rgba(187, 134, 252, 0.1);
}
/* Progress Bar */
.progress-container {
width: 100%;
height: 4px;
background-color: #1E1E1E;
border-radius: 2px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #BB86FC;
border-radius: 2px;
}
/* Toast */
.toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
background-color: #323232;
color: #FFFFFF;
font-size: 14px;
padding: 12px 24px;
border-radius: 8px;
z-index: 1001;
}
/* Badge */
.badge {
position: absolute;
top: 4px;
right: 4px;
background-color: #F44336;
color: #FFFFFF;
font-size: 12px;
font-weight: 600;
min-width: 18px;
height: 18px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
}
.store-nav-item {
position: relative;
}
</style>
</head>
<body class="store-screen">
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()">
<img src="../../icons/back.tga" style="width: 32px; height: 32px;"/>
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">Mosis Store</span>
<div class="btn-icon">
<img src="../../icons/account.tga" style="width: 32px; height: 32px;"/>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/account.tga"/>
</div>
</div>
</div>
@@ -447,7 +318,6 @@
<div class="category-chip">Games</div>
<div class="category-chip">Social</div>
<div class="category-chip">Productivity</div>
<div class="category-chip">Entertainment</div>
<div class="category-chip">Tools</div>
</div>
@@ -490,15 +360,11 @@
<span class="section-action">See all</span>
</div>
<!-- App List -->
<div class="app-list-item">
<div class="app-list-icon bg-purple">S</div>
<div class="app-list-info">
<div class="app-list-name">Social Hub</div>
<div class="app-list-meta">Social &bull; 12 MB</div>
<div class="app-list-rating">
<span>4.9 &bull; 1.2M downloads</span>
</div>
<div class="app-list-meta">Social - 12 MB - 4.9</div>
</div>
<div class="install-btn">Install</div>
</div>
@@ -507,10 +373,7 @@
<div class="app-list-icon bg-red">G</div>
<div class="app-list-info">
<div class="app-list-name">Games Center</div>
<div class="app-list-meta">Games &bull; 45 MB</div>
<div class="app-list-rating">
<span>4.7 &bull; 890K downloads</span>
</div>
<div class="app-list-meta">Games - 45 MB - 4.7</div>
</div>
<div class="install-btn">Install</div>
</div>
@@ -519,10 +382,7 @@
<div class="app-list-icon bg-indigo">F</div>
<div class="app-list-info">
<div class="app-list-name">File Manager</div>
<div class="app-list-meta">Tools &bull; 8 MB</div>
<div class="app-list-rating">
<span>4.6 &bull; 650K downloads</span>
</div>
<div class="app-list-meta">Tools - 8 MB - 4.6</div>
</div>
<div class="install-btn installed">Open</div>
</div>
@@ -531,100 +391,26 @@
<div class="app-list-icon bg-pink">M</div>
<div class="app-list-info">
<div class="app-list-name">Music Player</div>
<div class="app-list-meta">Music &bull; 18 MB</div>
<div class="app-list-rating">
<span>4.5 &bull; 520K downloads</span>
</div>
<div class="app-list-meta">Music - 18 MB - 4.5</div>
</div>
<div class="install-btn">Install</div>
</div>
<div class="app-list-item">
<div class="app-list-icon bg-teal">P</div>
<div class="app-list-info">
<div class="app-list-name">Photo Editor</div>
<div class="app-list-meta">Photography &bull; 32 MB</div>
<div class="app-list-rating">
<span>4.4 &bull; 410K downloads</span>
</div>
</div>
<div class="install-btn">Install</div>
</div>
<!-- New Games Section -->
<div class="section-header">
<span class="section-title">New Games</span>
<span class="section-action">See all</span>
</div>
<div class="app-cards-row">
<div class="app-card">
<div class="app-card-icon bg-red">P</div>
<div class="app-card-name">Puzzle Quest</div>
<div class="app-card-category">Puzzle</div>
<div class="app-card-rating">4.8</div>
</div>
<div class="app-card">
<div class="app-card-icon bg-green">R</div>
<div class="app-card-name">Racing VR</div>
<div class="app-card-category">Racing</div>
<div class="app-card-rating">4.6</div>
</div>
<div class="app-card">
<div class="app-card-icon bg-blue">S</div>
<div class="app-card-name">Space Explorer</div>
<div class="app-card-category">Adventure</div>
<div class="app-card-rating">4.7</div>
</div>
<div class="app-card">
<div class="app-card-icon bg-orange">C</div>
<div class="app-card-name">Card Master</div>
<div class="app-card-category">Card</div>
<div class="app-card-rating">4.5</div>
</div>
</div>
</div>
<!-- Bottom Navigation -->
<div class="store-bottom-nav">
<div id="nav-apps" class="store-nav-item active" onclick="showHome()">
<div class="store-nav-item active">
<img src="../../icons/home.tga"/>
<span>Apps</span>
</div>
<div id="nav-games" class="store-nav-item" onclick="showGames()">
<div class="store-nav-item">
<img src="../../icons/game.tga"/>
<span>Games</span>
</div>
<div id="nav-updates" class="store-nav-item" onclick="showUpdates()">
<div class="store-nav-item">
<img src="../../icons/download.tga"/>
<span>Updates</span>
<div id="updates-badge" class="badge" style="display: none;"></div>
</div>
</div>
<!-- Progress Dialog (hidden by default) -->
<div id="progress-dialog" class="dialog-overlay" style="display: none;">
<div class="dialog">
<div id="progress-title" class="dialog-title">Installing...</div>
<div class="progress-container">
<div id="progress-bar" class="progress-bar" style="width: 0%;"></div>
</div>
<div id="progress-status" class="dialog-status">Preparing...</div>
</div>
</div>
<!-- Error Dialog (hidden by default) -->
<div id="error-dialog" class="dialog-overlay" style="display: none;">
<div class="dialog">
<div class="dialog-title">Error</div>
<div id="error-message" class="dialog-message"></div>
<div class="dialog-actions">
<div class="dialog-btn" onclick="hideError()">OK</div>
</div>
</div>
</div>
<!-- Toast (hidden by default) -->
<div id="toast" class="toast" style="display: none;"></div>
</body>
</rml>

View File

@@ -0,0 +1,103 @@
-- Layout System for Virtual Smartphone
-- Provides reusable UI component helpers
-- Requires navigation.lua to be loaded first
-- Icon paths (relative to assets/)
local ICON_PATH = "../../icons/"
-- Default icons
local icons = {
back = ICON_PATH .. "back.tga",
home = ICON_PATH .. "home.tga",
menu = ICON_PATH .. "menu.tga",
search = ICON_PATH .. "search.tga",
more = ICON_PATH .. "more.tga",
close = ICON_PATH .. "close.tga",
wifi = ICON_PATH .. "wifi.tga",
signal = ICON_PATH .. "signal.tga",
battery = ICON_PATH .. "battery.tga"
}
-- Get current time formatted as HH:MM
local function getCurrentTime()
-- In sandbox, we might not have os.date, use a default
if os and os.date then
return os.date("%H:%M")
end
return "12:30"
end
-- Update status bar time
function updateStatusTime(doc)
local timeEl = doc:GetElementById("status-time")
if timeEl then
timeEl.inner_rml = getCurrentTime()
end
end
-- Initialize status bar with current time
-- Call from document onload
function initStatusBar(doc)
updateStatusTime(doc)
-- Set up timer to update time every minute if timers are available
if setTimeout then
local function updateLoop()
updateStatusTime(doc)
setTimeout(updateLoop, 60000)
end
setTimeout(updateLoop, 60000)
end
end
-- Initialize app bar back button
-- Call from document onload
function initAppBar(doc)
local backBtn = doc:GetElementById("app-bar-back")
if backBtn then
-- Back button is handled via onclick in RML
-- This is for any additional setup
end
end
-- Initialize system navigation bar
-- Call from document onload
function initSystemNav(doc)
-- Navigation buttons are handled via onclick in RML
-- This is for any additional setup
end
-- Full layout initialization
-- Call from document onload: initLayout(document)
function initLayout(doc)
initStatusBar(doc)
initAppBar(doc)
initSystemNav(doc)
print("Layout initialized")
end
-- Handle back button press (for app bar or system nav)
function onBackPressed()
if canGoBack and canGoBack() then
goBack()
else
-- If at root, go home
if goHome then
goHome()
end
end
end
-- Handle home button press
function onHomePressed()
if goHome then
goHome()
end
end
-- Handle recent apps button press (placeholder)
function onRecentPressed()
print("Recent apps pressed (not implemented)")
end
print("Layout system loaded")

View File

@@ -0,0 +1,270 @@
/* ==============================================
Layout Components: Reusable App Layout Structure
System status bar, app bar, navigation bar
============================================== */
/* ============== System Status Bar ============== */
/* Top bar showing time, signal, wifi, battery */
.system-status-bar {
height: 36px;
padding: 0 16px;
background-color: transparent;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #FFFFFF;
z-index: 1000;
}
.system-status-bar.bg-surface {
background-color: #1E1E1E;
}
.system-status-time {
font-weight: 500;
font-size: 16px;
}
.system-status-icons {
display: flex;
gap: 8px;
align-items: center;
}
.system-status-icons img {
width: 24px;
height: 24px;
pointer-events: none;
}
/* ============== App Bar ============== */
/* Title bar with back button and optional actions */
.app-bar {
height: 72px;
padding: 0 8px;
background-color: #1E1E1E;
display: flex;
align-items: center;
z-index: 900;
}
.app-bar.transparent {
background-color: transparent;
}
.app-bar.primary {
background-color: #121212;
}
.app-bar-back {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 28px;
}
.app-bar-back:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.app-bar-back:active {
background-color: rgba(255, 255, 255, 0.2);
}
.app-bar-back img {
width: 32px;
height: 32px;
pointer-events: none;
}
.app-bar-title {
flex: 1;
font-size: 24px;
font-weight: 500;
color: #FFFFFF;
padding-left: 8px;
}
.app-bar-actions {
display: flex;
gap: 4px;
}
.app-bar-action {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 28px;
}
.app-bar-action:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.app-bar-action:active {
background-color: rgba(255, 255, 255, 0.2);
}
.app-bar-action img {
width: 28px;
height: 28px;
pointer-events: none;
}
/* ============== System Navigation Bar ============== */
/* Bottom bar with back, home, and recent buttons */
.system-nav-bar {
height: 56px;
background-color: #0A0A0A;
display: flex;
align-items: center;
justify-content: space-around;
z-index: 1000;
}
.system-nav-bar.transparent {
background-color: rgba(10, 10, 10, 0.9);
}
.system-nav-btn {
width: 72px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 24px;
}
.system-nav-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.system-nav-btn:active {
background-color: rgba(255, 255, 255, 0.2);
}
.system-nav-btn img {
width: 28px;
height: 28px;
pointer-events: none;
opacity: 0.8;
}
/* Home button - pill shape */
.system-nav-home {
width: 96px;
height: 8px;
background-color: #FFFFFF;
border-radius: 4px;
cursor: pointer;
opacity: 0.6;
}
.system-nav-home:hover {
opacity: 0.8;
}
.system-nav-home:active {
opacity: 1.0;
}
/* ============== Screen Layout ============== */
/* Standard app screen structure */
.app-screen {
width: 100%;
height: 100%;
background-color: #121212;
display: flex;
flex-direction: column;
}
.app-content {
flex: 1;
overflow: auto;
display: flex;
flex-direction: column;
}
/* Content padding for nav bar */
.app-content.with-nav {
padding-bottom: 56px;
}
/* Content padding for dock */
.app-content.with-dock {
padding-bottom: 100px;
}
/* ============== Combined Header ============== */
/* Status bar + App bar combined */
.app-header {
display: flex;
flex-direction: column;
background-color: #1E1E1E;
}
.app-header.transparent {
background-color: transparent;
}
.app-header .system-status-bar {
background-color: transparent;
}
/* ============== Notification Badge ============== */
.notification-badge {
position: absolute;
top: 8px;
right: 8px;
min-width: 20px;
height: 20px;
background-color: #CF6679;
border-radius: 10px;
font-size: 12px;
font-weight: 600;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
padding: 0 6px;
}
/* ============== Recording Indicator ============== */
/* Shown when camera/mic is active */
.recording-indicator {
position: absolute;
top: 8px;
right: 8px;
width: 12px;
height: 12px;
background-color: #F44336;
border-radius: 6px;
animation: recording-pulse 1.5s ease-in-out infinite;
}
@keyframes recording-pulse {
0%, 100% { opacity: 1.0; }
50% { opacity: 0.4; }
}
/* ============== Divider ============== */
.header-divider {
height: 1px;
background-color: #333333;
}

55
tests/README.md Normal file
View File

@@ -0,0 +1,55 @@
# UI Test Files
This directory contains JSON action playback tests for the Mosis Designer.
## Running Tests
```bash
# From designer/build directory
./Release/mosis-designer.exe --simulator --test-apps base-apps \
--playback ../../tests/test_settings.json \
--screenshot-after ../../tests/screenshots/result.png
```
## Test Files
| File | Description |
|------|-------------|
| `test_home_only.json` | Wait and capture home screen |
| `test_settings.json` | Navigate to Settings app |
| `test_browser.json` | Navigate to Browser (dock) |
| `test_messages.json` | Navigate to Messages app |
| `test_messages_v2.json` | Messages with corrected coordinates |
| `test_music.json` | Navigate to Music app |
| `test_store.json` | Navigate to Mosis Store |
| `test_navigation.json` | Multi-app navigation sequence |
## Screenshots
Captured screenshots are in `screenshots/`:
- `screenshot_home_fresh.png` - Home screen
- `screenshot_browser.png` - Browser app
- `screenshot_messages_fixed.png` - Messages app
- `screenshot_music_fixed.png` - Music app
- `screenshot_settings_fixed.png` - Settings app
- `screenshot_store_fixed.png` - Mosis Store
## Hierarchy Dumps
- `hierarchy_fresh.json` - Full UI element tree with bounds
- `hierarchy_dump.json` - Previous hierarchy capture
Use hierarchy dumps to find element coordinates for new tests.
## Prerequisites
Before running tests with `base-apps`, ensure shared assets exist at `MosisService/` root:
```bash
# Copy shared assets (run from MosisService/)
cp -r src/main/assets/ui .
cp -r src/main/assets/scripts .
cp -r src/main/assets/icons .
```
See `docs/TESTING-FRAMEWORK.md` for full documentation.

2583
tests/hierarchy_dump.json Normal file

File diff suppressed because it is too large Load Diff

1844
tests/hierarchy_fresh.json Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More