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>
|
||||
BIN
base-apps/com.mosis.sandbox-test.mosis
Normal file
BIN
base-apps/com.mosis.sandbox-test.mosis
Normal file
Binary file not shown.
223
base-apps/com.mosis.sandbox-test/app.lua
Normal file
223
base-apps/com.mosis.sandbox-test/app.lua
Normal file
@@ -0,0 +1,223 @@
|
||||
-- Sandbox Test App
|
||||
-- Tests: timers, JSON, crypto, storage
|
||||
|
||||
local results = {}
|
||||
local logCounter = 0
|
||||
|
||||
-- Helper to get document (use global set by C++)
|
||||
local function getDocument()
|
||||
-- The C++ code sets 'document' global after loading
|
||||
return document
|
||||
end
|
||||
|
||||
local function log(msg)
|
||||
logCounter = logCounter + 1
|
||||
table.insert(results, string.format("[%03d] %s", logCounter, msg))
|
||||
local doc = getDocument()
|
||||
if doc then
|
||||
local el = doc:GetElementById("results")
|
||||
if el then
|
||||
el.inner_rml = table.concat(results, "\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Navigation helper
|
||||
function goBack()
|
||||
if navigation and navigation.back then
|
||||
navigation.back()
|
||||
else
|
||||
log("Navigation not available")
|
||||
end
|
||||
end
|
||||
|
||||
local function setStatus(id, status, success)
|
||||
local doc = getDocument()
|
||||
if not doc then
|
||||
print("[LUA] ERROR: document not available!")
|
||||
return
|
||||
end
|
||||
local el = doc:GetElementById(id)
|
||||
if el then
|
||||
if success then
|
||||
el.inner_rml = "✓ " .. status
|
||||
else
|
||||
el.inner_rml = "✗ " .. status
|
||||
end
|
||||
else
|
||||
print("[LUA] ERROR: element not found: " .. id)
|
||||
end
|
||||
end
|
||||
|
||||
-- Timer test
|
||||
function testTimer()
|
||||
print("[LUA] testTimer called!")
|
||||
setStatus("timer-status", "Running...", true)
|
||||
log("Starting timer test...")
|
||||
|
||||
local count = 0
|
||||
local timerId = nil
|
||||
|
||||
timerId = setInterval(function()
|
||||
count = count + 1
|
||||
log("Timer tick: " .. count)
|
||||
|
||||
if count >= 3 then
|
||||
clearInterval(timerId)
|
||||
setStatus("timer-status", "Passed (3 ticks)", true)
|
||||
log("Timer test complete!")
|
||||
end
|
||||
end, 1000)
|
||||
|
||||
log("Timer started with ID: " .. tostring(timerId))
|
||||
end
|
||||
|
||||
-- JSON test
|
||||
function testJSON()
|
||||
log("Starting JSON test...")
|
||||
|
||||
local success = true
|
||||
local msg = ""
|
||||
|
||||
-- Test encode
|
||||
local data = {
|
||||
name = "test",
|
||||
value = 42,
|
||||
nested = { a = 1, b = 2 }
|
||||
}
|
||||
|
||||
local encoded = json.encode(data)
|
||||
if encoded then
|
||||
log("Encoded: " .. encoded)
|
||||
else
|
||||
success = false
|
||||
msg = "encode failed"
|
||||
end
|
||||
|
||||
-- Test decode
|
||||
if success then
|
||||
local decoded = json.decode(encoded)
|
||||
if decoded and decoded.name == "test" and decoded.value == 42 then
|
||||
log("Decoded successfully, name=" .. decoded.name)
|
||||
else
|
||||
success = false
|
||||
msg = "decode failed"
|
||||
end
|
||||
end
|
||||
|
||||
if success then
|
||||
setStatus("json-status", "Passed", true)
|
||||
log("JSON test complete!")
|
||||
else
|
||||
setStatus("json-status", "Failed: " .. msg, false)
|
||||
end
|
||||
end
|
||||
|
||||
-- Crypto test
|
||||
function testCrypto()
|
||||
log("Starting crypto test...")
|
||||
|
||||
local success = true
|
||||
local msg = ""
|
||||
|
||||
-- Test random bytes
|
||||
local bytes = crypto.randomBytes(16)
|
||||
if bytes and #bytes == 16 then
|
||||
log("Random bytes (hex): " .. bytes:gsub(".", function(c)
|
||||
return string.format("%02x", c:byte())
|
||||
end))
|
||||
else
|
||||
success = false
|
||||
msg = "randomBytes failed"
|
||||
end
|
||||
|
||||
-- Test SHA256
|
||||
if success then
|
||||
local hash = crypto.hash("sha256", "hello world")
|
||||
if hash then
|
||||
log("SHA256: " .. hash:sub(1, 32) .. "...")
|
||||
else
|
||||
success = false
|
||||
msg = "sha256 failed"
|
||||
end
|
||||
end
|
||||
|
||||
-- Test HMAC
|
||||
if success then
|
||||
local hmac = crypto.hmac("sha256", "secret", "message")
|
||||
if hmac then
|
||||
log("HMAC: " .. hmac:sub(1, 32) .. "...")
|
||||
else
|
||||
success = false
|
||||
msg = "hmac failed"
|
||||
end
|
||||
end
|
||||
|
||||
if success then
|
||||
setStatus("crypto-status", "Passed", true)
|
||||
log("Crypto test complete!")
|
||||
else
|
||||
setStatus("crypto-status", "Failed: " .. msg, false)
|
||||
end
|
||||
end
|
||||
|
||||
-- Storage test
|
||||
function testStorage()
|
||||
log("Starting storage test...")
|
||||
|
||||
local success = true
|
||||
local msg = ""
|
||||
|
||||
-- Test write (VirtualFS requires /data/, /cache/, /temp/, or /shared/ prefix)
|
||||
local writeOk = fs.write("/data/test.txt", "Hello from sandbox!")
|
||||
if writeOk then
|
||||
log("Write successful")
|
||||
else
|
||||
success = false
|
||||
msg = "write failed"
|
||||
end
|
||||
|
||||
-- Test read
|
||||
if success then
|
||||
local content = fs.read("/data/test.txt")
|
||||
if content == "Hello from sandbox!" then
|
||||
log("Read successful: " .. content)
|
||||
else
|
||||
success = false
|
||||
msg = "read mismatch"
|
||||
end
|
||||
end
|
||||
|
||||
-- Test list
|
||||
if success then
|
||||
local files = fs.list("/data")
|
||||
if files then
|
||||
log("Files in /data: " .. #files)
|
||||
for _, f in ipairs(files) do
|
||||
log(" - " .. f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Test delete
|
||||
if success then
|
||||
local deleteOk = fs.delete("/data/test.txt")
|
||||
if deleteOk then
|
||||
log("Delete successful")
|
||||
else
|
||||
success = false
|
||||
msg = "delete failed"
|
||||
end
|
||||
end
|
||||
|
||||
if success then
|
||||
setStatus("storage-status", "Passed", true)
|
||||
log("Storage test complete!")
|
||||
else
|
||||
setStatus("storage-status", "Failed: " .. msg, false)
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize
|
||||
log("Sandbox Test App loaded")
|
||||
log("Lua version: " .. (_VERSION or "unknown"))
|
||||
BIN
base-apps/com.mosis.sandbox-test/icon.tga
LFS
Normal file
BIN
base-apps/com.mosis.sandbox-test/icon.tga
LFS
Normal file
Binary file not shown.
46
base-apps/com.mosis.sandbox-test/main.rml
Normal file
46
base-apps/com.mosis.sandbox-test/main.rml
Normal file
@@ -0,0 +1,46 @@
|
||||
<rml>
|
||||
<head>
|
||||
<title>Sandbox Test</title>
|
||||
<link type="text/rcss" href="styles.rcss"/>
|
||||
<script src="app.lua"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-bar">
|
||||
<div class="app-bar-nav btn-icon" onclick="goHome()">
|
||||
<span class="icon"><</span>
|
||||
</div>
|
||||
<div class="app-bar-title">Sandbox Test</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="card">
|
||||
<div class="card-title">Timer Test</div>
|
||||
<div id="timer-status">Not started</div>
|
||||
<button onclick="testTimer()">Start Timer</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">JSON Test</div>
|
||||
<div id="json-status">Not tested</div>
|
||||
<button onclick="testJSON()">Test JSON</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Crypto Test</div>
|
||||
<div id="crypto-status">Not tested</div>
|
||||
<button onclick="testCrypto()">Test Crypto</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Storage Test</div>
|
||||
<div id="storage-status">Not tested</div>
|
||||
<button onclick="testStorage()">Test Storage</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Results</div>
|
||||
<div id="results">Click buttons above to run tests</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
18
base-apps/com.mosis.sandbox-test/manifest.json
Normal file
18
base-apps/com.mosis.sandbox-test/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "com.mosis.sandbox-test",
|
||||
"name": "Sandbox Test",
|
||||
"version": "1.0.0",
|
||||
"version_code": 1,
|
||||
"entry": "main.rml",
|
||||
"icon": "icon.tga",
|
||||
"description": "Tests sandbox APIs: timers, storage, JSON, crypto",
|
||||
"developer": {
|
||||
"name": "Mosis Team",
|
||||
"email": "dev@mosis.dev"
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"network"
|
||||
],
|
||||
"min_api_version": 1
|
||||
}
|
||||
97
base-apps/com.mosis.sandbox-test/styles.rcss
Normal file
97
base-apps/com.mosis.sandbox-test/styles.rcss
Normal file
@@ -0,0 +1,97 @@
|
||||
body {
|
||||
font-family: LatoLatin;
|
||||
font-size: 16dp;
|
||||
background-color: #121212;
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.app-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 56dp;
|
||||
background-color: #1e1e1e;
|
||||
padding: 0 8dp;
|
||||
}
|
||||
|
||||
.app-bar-nav {
|
||||
width: 40dp;
|
||||
height: 40dp;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 20dp;
|
||||
}
|
||||
|
||||
.app-bar-nav:hover {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 24dp;
|
||||
}
|
||||
|
||||
.app-bar-title {
|
||||
font-size: 20dp;
|
||||
font-weight: bold;
|
||||
margin-left: 16dp;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
padding: 16dp;
|
||||
width: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: block;
|
||||
background-color: #1e1e1e;
|
||||
border-radius: 12dp;
|
||||
padding: 16dp;
|
||||
margin-bottom: 12dp;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
font-size: 18dp;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8dp;
|
||||
color: #bb86fc;
|
||||
}
|
||||
|
||||
.card div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
background-color: #bb86fc;
|
||||
color: #000000;
|
||||
border-width: 0;
|
||||
border-radius: 8dp;
|
||||
padding: 12dp 24dp;
|
||||
font-size: 14dp;
|
||||
font-weight: bold;
|
||||
margin-top: 8dp;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #cf9fff;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: #9a67ea;
|
||||
}
|
||||
|
||||
#results {
|
||||
font-family: LatoLatin;
|
||||
font-size: 12dp;
|
||||
background-color: #0d0d0d;
|
||||
padding: 12dp;
|
||||
border-radius: 8dp;
|
||||
white-space: pre-wrap;
|
||||
color: #00ff00;
|
||||
}
|
||||
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.
21
base-apps/package.bat
Normal file
21
base-apps/package.bat
Normal file
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
REM Package test apps as .mosis files
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
for /d %%d in (*) do (
|
||||
if exist "%%d\manifest.json" (
|
||||
echo Packaging %%d...
|
||||
cd %%d
|
||||
if exist "..\%%d.mosis" del "..\%%d.mosis"
|
||||
tar -a -cf "..\%%d.mosis" *
|
||||
cd ..
|
||||
echo Created %%d.mosis
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Done! Package files:
|
||||
dir /b *.mosis 2>nul
|
||||
|
||||
endlocal
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user