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:
247
base-apps/com.mosis.browser/browser.rml
Normal file
247
base-apps/com.mosis.browser/browser.rml
Normal 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>
|
||||
18
base-apps/com.mosis.browser/manifest.json
Normal file
18
base-apps/com.mosis.browser/manifest.json
Normal 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
|
||||
}
|
||||
368
base-apps/com.mosis.camera/camera.rml
Normal file
368
base-apps/com.mosis.camera/camera.rml
Normal 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>
|
||||
19
base-apps/com.mosis.camera/manifest.json
Normal file
19
base-apps/com.mosis.camera/manifest.json
Normal 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
|
||||
}
|
||||
161
base-apps/com.mosis.contacts/contact_detail.rml
Normal file
161
base-apps/com.mosis.contacts/contact_detail.rml
Normal 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>
|
||||
321
base-apps/com.mosis.contacts/contacts.rml
Normal file
321
base-apps/com.mosis.contacts/contacts.rml
Normal 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>
|
||||
18
base-apps/com.mosis.contacts/manifest.json
Normal file
18
base-apps/com.mosis.contacts/manifest.json
Normal 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
|
||||
}
|
||||
126
base-apps/com.mosis.dialer/calling.rml
Normal file
126
base-apps/com.mosis.dialer/calling.rml
Normal 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>
|
||||
157
base-apps/com.mosis.dialer/dialer.rml
Normal file
157
base-apps/com.mosis.dialer/dialer.rml
Normal 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>
|
||||
18
base-apps/com.mosis.dialer/manifest.json
Normal file
18
base-apps/com.mosis.dialer/manifest.json
Normal 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
|
||||
}
|
||||
200
base-apps/com.mosis.home/home.lua
Normal file
200
base-apps/com.mosis.home/home.lua
Normal file
@@ -0,0 +1,200 @@
|
||||
-- home.lua - Home screen dynamic app rendering
|
||||
-- Handles system apps and discovered third-party apps
|
||||
|
||||
-- System apps with their navigation keys and colors
|
||||
local system_apps = {
|
||||
-- Row 1
|
||||
{name = "Phone", icon = "phone", color = "#4CAF50", nav = "dialer"},
|
||||
{name = "Messages", icon = "message", color = "#2196F3", nav = "messages"},
|
||||
{name = "Contacts", icon = "contacts", color = "#FF9800", nav = "contacts"},
|
||||
{name = "Browser", icon = "browser", color = "#F44336", nav = "browser"},
|
||||
-- Row 2
|
||||
{name = "Gallery", icon = "gallery", color = "#9C27B0", nav = nil},
|
||||
{name = "Camera", icon = "camera", color = "#00BCD4", nav = "camera"},
|
||||
{name = "Settings", icon = "settings", color = "#607D8B", nav = "settings"},
|
||||
{name = "Music", icon = "music", color = "#E91E63", nav = "music"},
|
||||
-- Row 3
|
||||
{name = "Calendar", icon = "calendar", color = "#3F51B5", nav = nil},
|
||||
{name = "Clock", icon = "clock", color = "#009688", nav = nil},
|
||||
{name = "Notes", icon = "notes", color = "#795548", nav = nil},
|
||||
{name = "Maps", icon = "maps", color = "#FF5722", nav = nil},
|
||||
-- Row 4
|
||||
{name = "Store", icon = "store", color = "#8BC34A", nav = "store"},
|
||||
{name = "Files", icon = "files", color = "#CDDC39", nav = nil},
|
||||
{name = "Calculator", icon = "calculator", color = "#FFC107", nav = nil},
|
||||
{name = "Weather", icon = "weather", color = "#673AB7", nav = nil},
|
||||
}
|
||||
|
||||
-- State
|
||||
local installed_apps = {}
|
||||
local home_document = nil -- Store document reference
|
||||
|
||||
-- Initialize on load (receives document from onload event)
|
||||
function initHome(doc)
|
||||
print("[Home] Initializing home screen...")
|
||||
home_document = doc
|
||||
|
||||
-- Get installed third-party apps
|
||||
if mosis and mosis.apps then
|
||||
installed_apps = mosis.apps.getInstalled() or {}
|
||||
print("[Home] Found " .. #installed_apps .. " installed apps")
|
||||
|
||||
-- Filter to only third-party (non-system) apps
|
||||
local third_party = {}
|
||||
for _, app in ipairs(installed_apps) do
|
||||
if not app.is_system_app then
|
||||
table.insert(third_party, app)
|
||||
print("[Home] Third-party app: " .. app.name .. " (" .. app.package_id .. ")")
|
||||
end
|
||||
end
|
||||
installed_apps = third_party
|
||||
else
|
||||
print("[Home] Warning: mosis.apps API not available")
|
||||
installed_apps = {}
|
||||
end
|
||||
|
||||
-- Render dynamic apps
|
||||
renderThirdPartyApps()
|
||||
end
|
||||
|
||||
-- Generate a color based on package_id
|
||||
function getAppColor(package_id)
|
||||
local colors = {
|
||||
"#BB86FC", "#03DAC6", "#FF9800", "#2196F3",
|
||||
"#4CAF50", "#F44336", "#E91E63", "#3F51B5",
|
||||
"#009688", "#795548", "#FF5722", "#673AB7"
|
||||
}
|
||||
|
||||
-- Simple hash of package_id to pick a color
|
||||
local hash = 0
|
||||
for i = 1, #package_id do
|
||||
hash = hash + package_id:byte(i)
|
||||
end
|
||||
|
||||
return colors[(hash % #colors) + 1]
|
||||
end
|
||||
|
||||
-- Get first letter for placeholder icon
|
||||
function getAppInitial(name)
|
||||
return name:sub(1, 1):upper()
|
||||
end
|
||||
|
||||
-- Render third-party apps into the grid
|
||||
function renderThirdPartyApps()
|
||||
-- Use stored document reference
|
||||
if not home_document then
|
||||
print("[Home] Could not get document reference")
|
||||
return
|
||||
end
|
||||
|
||||
local grid = home_document:GetElementById("third-party-apps")
|
||||
if not grid then
|
||||
print("[Home] third-party-apps container not found")
|
||||
return
|
||||
end
|
||||
|
||||
-- Clear existing content
|
||||
grid.inner_rml = ""
|
||||
|
||||
if #installed_apps == 0 then
|
||||
print("[Home] No third-party apps to display")
|
||||
return
|
||||
end
|
||||
|
||||
-- Build HTML for each app
|
||||
local html = ""
|
||||
for _, app in ipairs(installed_apps) do
|
||||
local color = getAppColor(app.package_id)
|
||||
local initial = getAppInitial(app.name)
|
||||
local icon_html
|
||||
|
||||
-- Check if app has an icon
|
||||
if app.icon and app.icon ~= "" then
|
||||
local icon_path
|
||||
-- Check if icon is already a full path (starts with / or contains :/)
|
||||
if app.icon:sub(1, 1) == "/" or app.icon:find(":/") then
|
||||
-- Already a full path
|
||||
icon_path = app.icon
|
||||
elseif app.install_path and app.install_path ~= "" then
|
||||
-- Relative filename - construct full path from install_path
|
||||
icon_path = app.install_path .. "/" .. app.icon
|
||||
else
|
||||
icon_path = app.icon
|
||||
end
|
||||
-- Use file:// prefix for absolute paths to prevent RmlUi URL resolution
|
||||
local src_path = icon_path
|
||||
if icon_path:sub(1, 1) == "/" then
|
||||
src_path = "file://" .. icon_path
|
||||
end
|
||||
-- Use img tag for actual icon
|
||||
icon_html = '<img src="' .. src_path .. '" style="width: 48px; height: 48px;"/>'
|
||||
print("[Home] Loading icon: " .. src_path)
|
||||
else
|
||||
-- Fallback to initial letter
|
||||
icon_html = '<span style="font-size: 28px; color: #000000;">' .. initial .. '</span>'
|
||||
end
|
||||
|
||||
html = html .. [[
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: ]] .. color .. [[;"
|
||||
onclick="launchThirdPartyApp(']] .. app.package_id .. [[')">
|
||||
]] .. icon_html .. [[
|
||||
</div>
|
||||
<span class="app-icon-label">]] .. app.name .. [[</span>
|
||||
</div>
|
||||
]]
|
||||
end
|
||||
|
||||
grid.inner_rml = html
|
||||
print("[Home] Rendered " .. #installed_apps .. " third-party apps")
|
||||
end
|
||||
|
||||
-- Get app info by package_id
|
||||
function getAppInfo(package_id)
|
||||
for _, app in ipairs(installed_apps) do
|
||||
if app.package_id == package_id then
|
||||
return app
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Launch a third-party app
|
||||
function launchThirdPartyApp(package_id)
|
||||
print("[Home] Launching app: " .. package_id)
|
||||
|
||||
if mosis and mosis.apps then
|
||||
local success = mosis.apps.launch(package_id)
|
||||
if success then
|
||||
print("[Home] App sandbox started: " .. package_id)
|
||||
|
||||
-- Get app info for sandbox switching and UI loading
|
||||
local app_info = getAppInfo(package_id)
|
||||
if app_info and app_info.install_path and app_info.entry_point then
|
||||
-- Switch sandbox context to this app (registers timer, fs, json, crypto APIs)
|
||||
if switchAppSandbox then
|
||||
switchAppSandbox(package_id, app_info.install_path)
|
||||
print("[Home] Sandbox context switched to: " .. package_id)
|
||||
end
|
||||
|
||||
-- Now load the app's UI document
|
||||
local entry_path = app_info.install_path .. "/" .. app_info.entry_point
|
||||
print("[Home] Loading app screen: " .. entry_path)
|
||||
local loaded = loadScreen(entry_path)
|
||||
if loaded then
|
||||
print("[Home] App UI loaded: " .. package_id)
|
||||
else
|
||||
print("[Home] Failed to load app UI: " .. entry_path)
|
||||
end
|
||||
else
|
||||
print("[Home] App info missing entry point: " .. package_id)
|
||||
end
|
||||
else
|
||||
print("[Home] Failed to launch app: " .. package_id)
|
||||
end
|
||||
else
|
||||
print("[Home] Cannot launch app: mosis.apps not available")
|
||||
end
|
||||
end
|
||||
|
||||
-- initHome() is called via onload in home.rml
|
||||
176
base-apps/com.mosis.home/home.rml
Normal file
176
base-apps/com.mosis.home/home.rml
Normal file
@@ -0,0 +1,176 @@
|
||||
<rml>
|
||||
<head>
|
||||
<link type="text/rcss" href="../../ui/html.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/theme.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/components.rcss"/>
|
||||
<script src="../../scripts/navigation.lua"></script>
|
||||
<script src="home.lua"></script>
|
||||
<title>Virtual Smartphone - Home</title>
|
||||
<style>
|
||||
.home-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #121212;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-content {
|
||||
flex: 1;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.app-icon-image img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dock-item img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.status-bar-icons img {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Third-party apps section */
|
||||
.app-grid-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Third-party apps use same sizing as system apps */
|
||||
#third-party-apps .app-icon {
|
||||
width: 25%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#third-party-apps .app-icon-image {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 12px auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#third-party-apps .app-icon-image:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#third-party-apps .app-icon-label {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="home-screen" onload="initHome(document)">
|
||||
<!-- Status Bar -->
|
||||
<div class="status-bar">
|
||||
<span class="status-bar-time">12:30</span>
|
||||
<div class="status-bar-icons">
|
||||
<img src="../../icons/wifi.tga" style="width: 24px; height: 24px;"/>
|
||||
<img src="../../icons/signal.tga" style="width: 24px; height: 24px;"/>
|
||||
<img src="../../icons/battery.tga" style="width: 24px; height: 24px;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App Grid -->
|
||||
<div class="home-content">
|
||||
<div class="app-grid">
|
||||
<!-- Row 1 -->
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #4CAF50;" onclick="navigateTo('dialer')"><img src="../../icons/phone.tga"/></div>
|
||||
<span class="app-icon-label">Phone</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #2196F3;" onclick="navigateTo('messages')"><img src="../../icons/message.tga"/></div>
|
||||
<span class="app-icon-label">Messages</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #FF9800;" onclick="navigateTo('contacts')"><img src="../../icons/contacts.tga"/></div>
|
||||
<span class="app-icon-label">Contacts</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #F44336;" onclick="navigateTo('browser')"><img src="../../icons/browser.tga"/></div>
|
||||
<span class="app-icon-label">Browser</span>
|
||||
</div>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #9C27B0;"><img src="../../icons/gallery.tga"/></div>
|
||||
<span class="app-icon-label">Gallery</span>
|
||||
</div>
|
||||
<div id="app-camera" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #00BCD4;" onclick="navigateTo('camera')"><img src="../../icons/camera.tga"/></div>
|
||||
<span class="app-icon-label">Camera</span>
|
||||
</div>
|
||||
<div id="app-settings" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #607D8B;" onclick="navigateTo('settings')"><img src="../../icons/settings.tga"/></div>
|
||||
<span class="app-icon-label">Settings</span>
|
||||
</div>
|
||||
<div id="app-music" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #E91E63;" onclick="navigateTo('music')"><img src="../../icons/music.tga"/></div>
|
||||
<span class="app-icon-label">Music</span>
|
||||
</div>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #3F51B5;"><img src="../../icons/calendar.tga"/></div>
|
||||
<span class="app-icon-label">Calendar</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #009688;"><img src="../../icons/clock.tga"/></div>
|
||||
<span class="app-icon-label">Clock</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #795548;"><img src="../../icons/notes.tga"/></div>
|
||||
<span class="app-icon-label">Notes</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #FF5722;"><img src="../../icons/maps.tga"/></div>
|
||||
<span class="app-icon-label">Maps</span>
|
||||
</div>
|
||||
|
||||
<!-- Row 4 -->
|
||||
<div id="app-store" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #8BC34A;" onclick="navigateTo('store')"><img src="../../icons/store.tga"/></div>
|
||||
<span class="app-icon-label">Store</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #CDDC39;"><img src="../../icons/files.tga"/></div>
|
||||
<span class="app-icon-label">Files</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #FFC107;"><img src="../../icons/calculator.tga"/></div>
|
||||
<span class="app-icon-label">Calculator</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #673AB7;"><img src="../../icons/weather.tga"/></div>
|
||||
<span class="app-icon-label">Weather</span>
|
||||
</div>
|
||||
|
||||
<!-- Third-party apps (dynamically populated by home.lua) -->
|
||||
<div id="third-party-apps" class="app-grid-section">
|
||||
<!-- Apps will be rendered here by home.lua -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dock -->
|
||||
<div class="dock">
|
||||
<div id="dock-phone" class="dock-item" style="background-color: #4CAF50;" onclick="navigateTo('dialer')"><img src="../../icons/phone.tga"/></div>
|
||||
<div id="dock-messages" class="dock-item" style="background-color: #2196F3;" onclick="navigateTo('messages')"><img src="../../icons/message.tga"/></div>
|
||||
<div id="dock-contacts" class="dock-item" style="background-color: #FF9800;" onclick="navigateTo('contacts')"><img src="../../icons/contacts.tga"/></div>
|
||||
<div id="dock-browser" class="dock-item" style="background-color: #F44336;" onclick="navigateTo('browser')"><img src="../../icons/browser.tga"/></div>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
176
base-apps/com.mosis.home/lock.rml
Normal file
176
base-apps/com.mosis.home/lock.rml
Normal file
@@ -0,0 +1,176 @@
|
||||
<rml>
|
||||
<head>
|
||||
<link type="text/rcss" href="../../ui/html.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/theme.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/components.rcss"/>
|
||||
<script src="../../scripts/navigation.lua"></script>
|
||||
<title>Lock Screen</title>
|
||||
<style>
|
||||
.lock-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #121212;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.lock-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lock-time {
|
||||
font-size: 72px;
|
||||
font-weight: 200;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.lock-date {
|
||||
font-size: 18px;
|
||||
color: #B3B3B3;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.lock-swipe {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lock-swipe-text {
|
||||
font-size: 18px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.lock-swipe-icon {
|
||||
font-size: 24px;
|
||||
color: #666666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.lock-shortcuts {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.lock-shortcut {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 28px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lock-shortcut:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.lock-notifications {
|
||||
margin-top: 48px;
|
||||
width: 100%;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.lock-notification {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lock-notification-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background-color: #2196F3;
|
||||
margin-right: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.lock-notification-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.lock-notification-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.lock-notification-text {
|
||||
font-size: 16px;
|
||||
color: #B3B3B3;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="lock-screen" onclick="goHome()">
|
||||
<!-- Status Bar -->
|
||||
<div class="status-bar">
|
||||
<span class="status-bar-time">12:30</span>
|
||||
<div class="status-bar-icons">
|
||||
<span>*</span>
|
||||
<span>+</span>
|
||||
<span>|</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="lock-content">
|
||||
<div class="lock-time">12:30</div>
|
||||
<div class="lock-date">Wednesday, January 15</div>
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class="lock-notifications">
|
||||
<div class="lock-notification">
|
||||
<div class="lock-notification-icon">M</div>
|
||||
<div class="lock-notification-content">
|
||||
<div class="lock-notification-title">Messages</div>
|
||||
<div class="lock-notification-text">John: Hey, are you coming to...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lock-notification">
|
||||
<div class="lock-notification-icon" style="background-color: #4CAF50;">P</div>
|
||||
<div class="lock-notification-content">
|
||||
<div class="lock-notification-title">Missed Call</div>
|
||||
<div class="lock-notification-text">Mom - 10 minutes ago</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Swipe to unlock -->
|
||||
<div class="lock-swipe">
|
||||
<div class="lock-swipe-icon">^</div>
|
||||
<div class="lock-swipe-text">Swipe up to unlock</div>
|
||||
</div>
|
||||
|
||||
<!-- Shortcuts -->
|
||||
<div class="lock-shortcuts">
|
||||
<div class="lock-shortcut" onclick="navigateTo('dialer')"><img src="../../icons/phone.tga" style="width: 32px; height: 32px;"/></div>
|
||||
<div class="lock-shortcut"><img src="../../icons/camera.tga" style="width: 32px; height: 32px;"/></div>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
16
base-apps/com.mosis.home/manifest.json
Normal file
16
base-apps/com.mosis.home/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "com.mosis.home",
|
||||
"name": "Home",
|
||||
"version": "1.0.0",
|
||||
"version_code": 1,
|
||||
"entry": "home.rml",
|
||||
"icon": "/system/icons/home.tga",
|
||||
"description": "Mosis home screen and app launcher",
|
||||
"developer": {
|
||||
"name": "Mosis Team",
|
||||
"email": "dev@mosis.dev"
|
||||
},
|
||||
"is_system_app": true,
|
||||
"permissions": [],
|
||||
"min_api_version": 1
|
||||
}
|
||||
164
base-apps/com.mosis.messages/chat.rml
Normal file
164
base-apps/com.mosis.messages/chat.rml
Normal 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>
|
||||
18
base-apps/com.mosis.messages/manifest.json
Normal file
18
base-apps/com.mosis.messages/manifest.json
Normal 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
|
||||
}
|
||||
220
base-apps/com.mosis.messages/messages.rml
Normal file
220
base-apps/com.mosis.messages/messages.rml
Normal 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>
|
||||
18
base-apps/com.mosis.music/manifest.json
Normal file
18
base-apps/com.mosis.music/manifest.json
Normal 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
|
||||
}
|
||||
415
base-apps/com.mosis.music/music.rml
Normal file
415
base-apps/com.mosis.music/music.rml
Normal 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>
|
||||
16
base-apps/com.mosis.settings/manifest.json
Normal file
16
base-apps/com.mosis.settings/manifest.json
Normal 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
|
||||
}
|
||||
360
base-apps/com.mosis.settings/settings.rml
Normal file
360
base-apps/com.mosis.settings/settings.rml
Normal 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 & 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>
|
||||
19
base-apps/com.mosis.store/manifest.json
Normal file
19
base-apps/com.mosis.store/manifest.json
Normal 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
|
||||
}
|
||||
394
base-apps/com.mosis.store/store.lua
Normal file
394
base-apps/com.mosis.store/store.lua
Normal 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()
|
||||
416
base-apps/com.mosis.store/store.rml
Normal file
416
base-apps/com.mosis.store/store.rml
Normal 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 & 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
BIN
base-apps/icons/account.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/add.tga
LFS
Normal file
BIN
base-apps/icons/add.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/back.tga
LFS
Normal file
BIN
base-apps/icons/back.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/backspace.tga
LFS
Normal file
BIN
base-apps/icons/backspace.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/battery.tga
LFS
Normal file
BIN
base-apps/icons/battery.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/browser.tga
LFS
Normal file
BIN
base-apps/icons/browser.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/calculator.tga
LFS
Normal file
BIN
base-apps/icons/calculator.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/calendar.tga
LFS
Normal file
BIN
base-apps/icons/calendar.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/call_small.tga
LFS
Normal file
BIN
base-apps/icons/call_small.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/camera.tga
LFS
Normal file
BIN
base-apps/icons/camera.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/clock.tga
LFS
Normal file
BIN
base-apps/icons/clock.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/close.tga
LFS
Normal file
BIN
base-apps/icons/close.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/contact_phone.tga
LFS
Normal file
BIN
base-apps/icons/contact_phone.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/contacts.tga
LFS
Normal file
BIN
base-apps/icons/contacts.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/dialpad.tga
LFS
Normal file
BIN
base-apps/icons/dialpad.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/download.tga
LFS
Normal file
BIN
base-apps/icons/download.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/files.tga
LFS
Normal file
BIN
base-apps/icons/files.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/flash.tga
LFS
Normal file
BIN
base-apps/icons/flash.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/forward.tga
LFS
Normal file
BIN
base-apps/icons/forward.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/gallery.tga
LFS
Normal file
BIN
base-apps/icons/gallery.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/game.tga
LFS
Normal file
BIN
base-apps/icons/game.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/heart.tga
LFS
Normal file
BIN
base-apps/icons/heart.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/history.tga
LFS
Normal file
BIN
base-apps/icons/history.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/home.tga
LFS
Normal file
BIN
base-apps/icons/home.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/library.tga
LFS
Normal file
BIN
base-apps/icons/library.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/maps.tga
LFS
Normal file
BIN
base-apps/icons/maps.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/menu.tga
LFS
Normal file
BIN
base-apps/icons/menu.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/message.tga
LFS
Normal file
BIN
base-apps/icons/message.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/more.tga
LFS
Normal file
BIN
base-apps/icons/more.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/music.tga
LFS
Normal file
BIN
base-apps/icons/music.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/notes.tga
LFS
Normal file
BIN
base-apps/icons/notes.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/phone.tga
LFS
Normal file
BIN
base-apps/icons/phone.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/play.tga
LFS
Normal file
BIN
base-apps/icons/play.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/refresh.tga
LFS
Normal file
BIN
base-apps/icons/refresh.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/search.tga
LFS
Normal file
BIN
base-apps/icons/search.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/send.tga
LFS
Normal file
BIN
base-apps/icons/send.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/settings.tga
LFS
Normal file
BIN
base-apps/icons/settings.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/signal.tga
LFS
Normal file
BIN
base-apps/icons/signal.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/store.tga
LFS
Normal file
BIN
base-apps/icons/store.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/switch-camera.tga
LFS
Normal file
BIN
base-apps/icons/switch-camera.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/timer.tga
LFS
Normal file
BIN
base-apps/icons/timer.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/weather.tga
LFS
Normal file
BIN
base-apps/icons/weather.tga
LFS
Normal file
Binary file not shown.
BIN
base-apps/icons/wifi.tga
LFS
Normal file
BIN
base-apps/icons/wifi.tga
LFS
Normal file
Binary file not shown.
103
base-apps/scripts/layout.lua
Normal file
103
base-apps/scripts/layout.lua
Normal 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")
|
||||
145
base-apps/scripts/navigation.lua
Normal file
145
base-apps/scripts/navigation.lua
Normal 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
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
93
base-apps/ui/html.rcss
Normal 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
270
base-apps/ui/layout.rcss
Normal 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
333
base-apps/ui/theme.rcss
Normal 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);
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
386
docs/BASE_APPS.md
Normal 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*
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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="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>
|
||||
<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">{{ 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>
|
||||
|
||||
@@ -3,201 +3,154 @@
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
@@ -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 .. ")")
|
||||
end
|
||||
local app_type = app.is_system_app and "System" or "Third-party"
|
||||
print("[Home] " .. app_type .. " app: " .. app.name .. " (" .. app.package_id .. ")")
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="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>
|
||||
<div class="conversation-content">
|
||||
<div class="conversation-header">
|
||||
<span class="conversation-name">{{ conv.name }}</span>
|
||||
<span class="conversation-time">{{ conv.time }}</span>
|
||||
<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 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,153 +154,206 @@
|
||||
}
|
||||
|
||||
.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="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 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>
|
||||
<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">W</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-title">Wi-Fi</div>
|
||||
<div class="settings-subtitle">Connected to MosisNetwork</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-toggle active">
|
||||
<div class="settings-toggle-thumb"></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>
|
||||
<div class="settings-item">
|
||||
<div class="settings-icon">B</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-title">Bluetooth</div>
|
||||
<div class="settings-subtitle">Off</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-toggle">
|
||||
<div class="settings-toggle-thumb"></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>
|
||||
<div class="settings-item">
|
||||
<div class="settings-icon">A</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-title">Airplane Mode</div>
|
||||
|
||||
<!-- Privacy Section -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-header">Privacy & 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-toggle">
|
||||
<div class="settings-toggle-thumb"></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>
|
||||
|
||||
<!-- Device Section -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-header">Device</div>
|
||||
<div class="settings-item">
|
||||
<div class="settings-icon">D</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">S</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">N</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">B</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">S</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>
|
||||
<!-- System Navigation Bar -->
|
||||
<div class="system-nav-bar">
|
||||
<div class="system-nav-btn" onclick="onBackPressed()">
|
||||
<img src="../../icons/back.tga"/>
|
||||
</div>
|
||||
|
||||
<!-- Privacy Section -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-header">Privacy & Security</div>
|
||||
<div class="settings-item">
|
||||
<div class="settings-icon">L</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">P</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">L</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">I</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-title">About Phone</div>
|
||||
<div class="settings-subtitle">Model, software version</div>
|
||||
</div>
|
||||
<span class="settings-action">></span>
|
||||
</div>
|
||||
<div class="system-nav-home" onclick="onHomePressed()"></div>
|
||||
<div class="system-nav-btn" onclick="onRecentPressed()">
|
||||
<img src="../../icons/menu.tga"/>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -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 • 12 MB</div>
|
||||
<div class="app-list-rating">
|
||||
<span>4.9 • 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 • 45 MB</div>
|
||||
<div class="app-list-rating">
|
||||
<span>4.7 • 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 • 8 MB</div>
|
||||
<div class="app-list-rating">
|
||||
<span>4.6 • 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 • 18 MB</div>
|
||||
<div class="app-list-rating">
|
||||
<span>4.5 • 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 • 32 MB</div>
|
||||
<div class="app-list-rating">
|
||||
<span>4.4 • 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>
|
||||
|
||||
103
src/main/assets/scripts/layout.lua
Normal file
103
src/main/assets/scripts/layout.lua
Normal 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")
|
||||
270
src/main/assets/ui/layout.rcss
Normal file
270
src/main/assets/ui/layout.rcss
Normal 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
55
tests/README.md
Normal 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
2583
tests/hierarchy_dump.json
Normal file
File diff suppressed because it is too large
Load Diff
1844
tests/hierarchy_fresh.json
Normal file
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
Reference in New Issue
Block a user