fix app layouts: remove style tags from content fragments, use component classes

This commit is contained in:
2026-01-20 17:40:38 +01:00
parent 4b47611902
commit 82bc0c78fe
25 changed files with 5350 additions and 537 deletions

View File

@@ -0,0 +1,307 @@
-- browser.lua - Web browser functionality
-- Handles URL navigation, tabs, bookmarks, and history
local browser_doc = nil
local tabs = {}
local current_tab_id = 1
local history = {}
local bookmarks = {}
-- Sample page content
local pages = {
["example.com"] = {
title = "Example Domain",
secure = true,
content = [[
<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" onclick="navigateToUrl('iana.org')">More information...</span>
</div>
]]
},
["google.com"] = {
title = "Google",
secure = true,
content = [[
<div style="text-align: center; padding: 60px 20px;">
<div style="font-size: 48px; font-weight: 500; color: #4285F4;">G</div>
<div style="font-size: 48px; font-weight: 500; color: #EA4335;">o</div>
<div style="font-size: 48px; font-weight: 500; color: #FBBC05;">o</div>
<div style="font-size: 48px; font-weight: 500; color: #4285F4;">g</div>
<div style="font-size: 48px; font-weight: 500; color: #34A853;">l</div>
<div style="font-size: 48px; font-weight: 500; color: #EA4335;">e</div>
<div style="margin-top: 32px;">
<input type="text" style="width: 80%; padding: 12px 20px; font-size: 16px; border-radius: 24px; background-color: #f1f3f4; border: none;" placeholder="Search Google"/>
</div>
</div>
]]
},
["mosis.app"] = {
title = "Mosis - Virtual Smartphone for VR",
secure = true,
content = [[
<div class="browser-page-title">Welcome to Mosis</div>
<div class="browser-page-text">
Mosis is a virtual smartphone OS for VR games and applications. Experience a phone-like device inside your virtual reality environment.
</div>
<div style="margin-top: 24px;">
<div class="browser-search-item" onclick="navigateToUrl('mosis.app/apps')">
<div class="browser-search-title">Browse Apps</div>
<div class="browser-search-desc">Discover apps for your virtual phone</div>
</div>
<div class="browser-search-item" onclick="navigateToUrl('mosis.app/developers')">
<div class="browser-search-title">For Developers</div>
<div class="browser-search-desc">Build apps for the Mosis platform</div>
</div>
</div>
]]
},
["default"] = {
title = "Page Not Found",
secure = false,
content = [[
<div style="text-align: center; padding: 60px 20px;">
<div style="font-size: 64px; color: #888888;">:(</div>
<div style="font-size: 24px; color: #333333; margin-top: 24px;">Page Not Found</div>
<div style="font-size: 16px; color: #666666; margin-top: 12px;">The requested page could not be loaded.</div>
</div>
]]
}
}
-- Initialize tabs
local function initTabs()
tabs = {
{id = 1, url = "example.com", title = "Example Domain"}
}
current_tab_id = 1
end
-- Initialize browser
function initBrowser(doc)
print("[Browser] Initializing...")
browser_doc = doc
initTabs()
loadPage(tabs[1].url)
end
-- Get current tab
local function getCurrentTab()
for _, tab in ipairs(tabs) do
if tab.id == current_tab_id then
return tab
end
end
return tabs[1]
end
-- Load a page
function loadPage(url)
if not browser_doc then return end
print("[Browser] Loading: " .. url)
-- Clean URL
url = url:gsub("^https?://", ""):gsub("^www%.", ""):gsub("/$", "")
-- Update current tab
local tab = getCurrentTab()
if tab then
tab.url = url
end
-- Add to history
table.insert(history, 1, {url = url, time = "Just now"})
-- Get page data
local page = pages[url] or pages["default"]
if tab then
tab.title = page.title
end
-- Update URL bar
local url_input = browser_doc:GetElementById("url-input")
if url_input then
url_input.value = url
end
-- Update secure icon
local secure_icon = browser_doc:GetElementById("secure-icon")
if secure_icon then
if page.secure then
secure_icon.inner_rml = "S"
secure_icon.style.color = "#4CAF50"
else
secure_icon.inner_rml = "!"
secure_icon.style.color = "#F44336"
end
end
-- Update page title
local title = browser_doc:GetElementById("page-title")
if title then
title.inner_rml = page.title
end
-- Update content
local content = browser_doc:GetElementById("browser-content")
if content then
content.inner_rml = [[<div class="browser-page">]] .. page.content .. [[</div>]]
end
-- Update tab count
updateTabCount()
end
-- Navigate to URL
function navigateToUrl(url)
loadPage(url)
end
-- Handle URL input
function onUrlSubmit()
if not browser_doc then return end
local input = browser_doc:GetElementById("url-input")
if input then
local url = input.value or ""
if url ~= "" then
loadPage(url)
end
end
end
-- Go back in history
function browserBack()
if #history > 1 then
table.remove(history, 1) -- Remove current page
local prev = history[1]
if prev then
loadPage(prev.url)
end
end
end
-- Go forward (simplified - just reload)
function browserForward()
if showToast then
showToast("No forward history")
end
end
-- Refresh page
function browserRefresh()
local tab = getCurrentTab()
if tab then
loadPage(tab.url)
if showToast then
showToast("Page refreshed")
end
end
end
-- Update tab count display
function updateTabCount()
if not browser_doc then return end
local count = browser_doc:GetElementById("tab-count")
if count then
count.inner_rml = tostring(#tabs)
end
end
-- Open new tab
function newTab()
local new_id = #tabs + 1
table.insert(tabs, {
id = new_id,
url = "mosis.app",
title = "New Tab"
})
current_tab_id = new_id
loadPage("mosis.app")
updateTabCount()
print("[Browser] New tab opened: " .. new_id)
end
-- Show tabs view
function showTabs()
print("[Browser] Show tabs")
if showToast then
showToast(#tabs .. " tab(s) open")
end
end
-- Close current tab
function closeTab()
if #tabs > 1 then
for i, tab in ipairs(tabs) do
if tab.id == current_tab_id then
table.remove(tabs, i)
break
end
end
current_tab_id = tabs[1].id
loadPage(tabs[1].url)
updateTabCount()
else
if showToast then
showToast("Cannot close last tab")
end
end
end
-- Add to bookmarks
function addBookmark()
local tab = getCurrentTab()
if tab then
table.insert(bookmarks, {
url = tab.url,
title = tab.title
})
if showToast then
showToast("Bookmark added")
end
end
end
-- Show bookmarks
function showBookmarks()
print("[Browser] Show bookmarks")
if showToast then
showToast(#bookmarks .. " bookmark(s)")
end
end
-- Show history
function showHistory()
print("[Browser] Show history")
if showToast then
showToast(#history .. " items in history")
end
end
-- Share page
function sharePage()
local tab = getCurrentTab()
if tab then
print("[Browser] Share: " .. tab.url)
if showToast then
showToast("Share: " .. tab.url)
end
end
end
-- Show menu
function showBrowserMenu()
print("[Browser] Show menu")
-- TODO: Show dropdown menu
end
-- Go to home
function browserHome()
loadPage("mosis.app")
end

View File

@@ -6,6 +6,7 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="browser.lua"></script>
<title>Browser</title>
<style>
.browser-toolbar {
@@ -30,12 +31,17 @@
background-color: rgba(255, 255, 255, 0.1);
}
.browser-nav-btn:active {
background-color: rgba(255, 255, 255, 0.2);
}
.browser-nav-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
.browser-nav-btn.disabled img {
.browser-nav-btn.disabled {
opacity: 0.3;
}
@@ -46,6 +52,11 @@
padding: 10px 16px;
background-color: #2D2D2D;
border-radius: 20px;
cursor: pointer;
}
.browser-url-bar:hover {
background-color: #3D3D3D;
}
.browser-secure-icon {
@@ -145,12 +156,14 @@
.browser-tab-btn:hover {
color: #FFFFFF;
background-color: rgba(255, 255, 255, 0.05);
}
.browser-tab-btn img {
width: 28px;
height: 28px;
margin-bottom: 4px;
pointer-events: none;
}
.browser-tab-btn span {
@@ -166,7 +179,7 @@
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="browser">
<body class="app-screen" onload="initLayout(document); initBrowser(document)" data-model="browser">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
@@ -179,66 +192,52 @@
<!-- Browser Toolbar -->
<div class="browser-toolbar">
<div class="browser-nav-btn" onclick="goBack()">
<div class="browser-nav-btn" onclick="browserBack()">
<img src="../../icons/back.tga"/>
</div>
<div class="browser-nav-btn disabled">
<div class="browser-nav-btn" onclick="browserForward()">
<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"/>
<span class="browser-secure-icon" id="secure-icon">S</span>
<input class="browser-url" type="text" value="example.com" id="url-input" onchange="onUrlSubmit()"/>
</div>
<div class="browser-nav-btn">
<div class="browser-nav-btn" onclick="browserRefresh()">
<img src="../../icons/refresh.tga"/>
</div>
<div class="browser-nav-btn">
<div class="browser-nav-btn" onclick="showBrowserMenu()">
<img src="../../icons/more.tga"/>
</div>
</div>
<!-- Browser Content -->
<div class="browser-content">
<div class="browser-content" id="browser-content">
<div class="browser-page">
<div class="browser-page-title">Example Domain</div>
<div class="browser-page-title" id="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>
<span class="browser-page-link" onclick="navigateToUrl('iana.org')">More information...</span>
</div>
</div>
</div>
<!-- Bottom Bar -->
<div class="browser-bottom-bar">
<div class="browser-tab-btn" onclick="goHome()">
<div class="browser-tab-btn" onclick="browserHome()">
<img src="../../icons/home.tga"/>
<span>Home</span>
</div>
<div class="browser-tab-btn">
<span class="browser-tabs-indicator">1</span>
<div class="browser-tab-btn" onclick="showTabs()">
<span class="browser-tabs-indicator" id="tab-count">1</span>
<span>Tabs</span>
</div>
<div class="browser-tab-btn">
<div class="browser-tab-btn" onclick="newTab()">
<img src="../../icons/add.tga"/>
<span>New Tab</span>
</div>
<div class="browser-tab-btn">
<div class="browser-tab-btn" onclick="showBrowserMenu()">
<img src="../../icons/menu.tga"/>
<span>Menu</span>
</div>

View File

@@ -0,0 +1,360 @@
-- camera.lua - Camera app functionality
-- Handles capture, modes, flash, zoom, and camera switching
local camera_doc = nil
local current_mode = "photo" -- photo, video, portrait, night
local flash_mode = "auto" -- auto, on, off
local timer_mode = "off" -- off, 3, 10
local is_front_camera = false
local is_recording = false
local zoom_level = 1.0
local photo_count = 0
local video_duration = 0
local video_timer_id = nil
-- Camera modes
local modes = {"Night", "Portrait", "Photo", "Video", "More"}
-- Initialize camera
function initCamera(doc)
print("[Camera] Initializing...")
camera_doc = doc
updateModeDisplay()
updateFlashDisplay()
updateTimerDisplay()
updateZoomDisplay()
end
-- Update mode display
function updateModeDisplay()
if not camera_doc then return end
for _, mode in ipairs(modes) do
local mode_el = camera_doc:GetElementById("mode-" .. mode:lower())
if mode_el then
if mode:lower() == current_mode then
mode_el:SetClass("active", true)
else
mode_el:SetClass("active", false)
end
end
end
-- Update capture button appearance for video mode
local capture_btn = camera_doc:GetElementById("capture-button")
if capture_btn then
if current_mode == "video" then
if is_recording then
capture_btn.inner_rml = [[<div class="capture-btn-stop"></div>]]
else
capture_btn.inner_rml = [[<div class="capture-btn-video"></div>]]
end
else
capture_btn.inner_rml = [[<div class="capture-btn-inner"></div>]]
end
end
end
-- Switch camera mode
function switchMode(mode)
print("[Camera] Switching to mode: " .. mode)
current_mode = mode:lower()
-- Stop recording if switching from video
if is_recording then
stopRecording()
end
updateModeDisplay()
if showToast then
showToast(mode .. " mode")
end
end
-- Toggle flash
function toggleFlash()
if flash_mode == "auto" then
flash_mode = "on"
elseif flash_mode == "on" then
flash_mode = "off"
else
flash_mode = "auto"
end
print("[Camera] Flash: " .. flash_mode)
updateFlashDisplay()
end
-- Update flash display
function updateFlashDisplay()
if not camera_doc then return end
local indicator = camera_doc:GetElementById("flash-indicator")
if indicator then
local text = "Flash: "
if flash_mode == "auto" then
text = text .. "Auto"
elseif flash_mode == "on" then
text = text .. "On"
else
text = text .. "Off"
end
indicator.inner_rml = text
end
end
-- Toggle timer
function toggleTimer()
if timer_mode == "off" then
timer_mode = "3"
elseif timer_mode == "3" then
timer_mode = "10"
else
timer_mode = "off"
end
print("[Camera] Timer: " .. timer_mode)
updateTimerDisplay()
end
-- Update timer display
function updateTimerDisplay()
if not camera_doc then return end
local indicator = camera_doc:GetElementById("timer-indicator")
if indicator then
local text = "Timer: "
if timer_mode == "off" then
text = text .. "Off"
else
text = text .. timer_mode .. "s"
end
indicator.inner_rml = text
end
end
-- Zoom in
function zoomIn()
if zoom_level < 10.0 then
zoom_level = math.min(zoom_level + 0.5, 10.0)
updateZoomDisplay()
end
end
-- Zoom out
function zoomOut()
if zoom_level > 0.5 then
zoom_level = math.max(zoom_level - 0.5, 0.5)
updateZoomDisplay()
end
end
-- Update zoom display
function updateZoomDisplay()
if not camera_doc then return end
local indicator = camera_doc:GetElementById("zoom-level")
if indicator then
indicator.inner_rml = string.format("%.1fx", zoom_level)
end
print("[Camera] Zoom: " .. zoom_level)
end
-- Switch camera (front/back)
function switchCamera()
is_front_camera = not is_front_camera
print("[Camera] Switched to " .. (is_front_camera and "front" or "back") .. " camera")
if showToast then
showToast(is_front_camera and "Front camera" or "Back camera")
end
-- Update viewfinder placeholder
local placeholder = camera_doc:GetElementById("viewfinder-placeholder")
if placeholder then
local icon = is_front_camera and "F" or "C"
placeholder.inner_rml = [[
<div class="viewfinder-placeholder-icon">]] .. icon .. [[</div>
<div>]] .. (is_front_camera and "Front Camera" or "Camera Preview") .. [[</div>
<div style="font-size: 14px; margin-top: 8px; color: #555555;">Tap to focus</div>
]]
end
end
-- Capture photo or start/stop video
function capture()
if current_mode == "video" then
if is_recording then
stopRecording()
else
startRecording()
end
else
takePhoto()
end
end
-- Take a photo
function takePhoto()
print("[Camera] Taking photo...")
-- Check timer
if timer_mode ~= "off" then
local delay = tonumber(timer_mode) * 1000
if showToast then
showToast("Timer: " .. timer_mode .. " seconds")
end
if setTimeout then
setTimeout(function()
actuallyTakePhoto()
end, delay)
else
actuallyTakePhoto()
end
else
actuallyTakePhoto()
end
end
-- Actually capture the photo
function actuallyTakePhoto()
photo_count = photo_count + 1
print("[Camera] Photo captured! Total: " .. photo_count)
-- Flash effect
if flash_mode == "on" or (flash_mode == "auto" and not is_front_camera) then
-- Simulate flash
end
-- Show capture animation/feedback
local viewfinder = camera_doc:GetElementById("camera-viewfinder")
if viewfinder then
viewfinder.style["background-color"] = "#FFFFFF"
if setTimeout then
setTimeout(function()
viewfinder.style["background-color"] = "#1a1a1a"
end, 100)
end
end
if showToast then
showToast("Photo saved")
end
-- Update gallery preview
updateGalleryPreview()
end
-- Start video recording
function startRecording()
print("[Camera] Starting recording...")
is_recording = true
video_duration = 0
updateModeDisplay()
-- Start timer
if setInterval then
video_timer_id = setInterval(function()
video_duration = video_duration + 1
updateRecordingTime()
end, 1000)
end
-- Show recording indicator
local indicator = camera_doc:GetElementById("recording-indicator")
if indicator then
indicator.style.display = "flex"
end
end
-- Stop video recording
function stopRecording()
print("[Camera] Stopping recording...")
is_recording = false
-- Stop timer
if video_timer_id and clearInterval then
clearInterval(video_timer_id)
video_timer_id = nil
end
updateModeDisplay()
-- Hide recording indicator
local indicator = camera_doc:GetElementById("recording-indicator")
if indicator then
indicator.style.display = "none"
end
if showToast then
local minutes = math.floor(video_duration / 60)
local seconds = video_duration % 60
showToast(string.format("Video saved (%02d:%02d)", minutes, seconds))
end
video_duration = 0
end
-- Update recording time display
function updateRecordingTime()
if not camera_doc then return end
local time_el = camera_doc:GetElementById("recording-time")
if time_el then
local minutes = math.floor(video_duration / 60)
local seconds = video_duration % 60
time_el.inner_rml = string.format("%02d:%02d", minutes, seconds)
end
end
-- Update gallery preview
function updateGalleryPreview()
-- In a real app, this would show the last captured photo
local preview = camera_doc:GetElementById("gallery-preview")
if preview then
preview.style["background-color"] = "#4CAF50"
end
end
-- Open gallery
function openGallery()
print("[Camera] Opening gallery...")
if navigateTo then
navigateTo("gallery")
else
if showToast then
showToast("Gallery: " .. photo_count .. " photos")
end
end
end
-- Open camera settings
function openCameraSettings()
print("[Camera] Opening settings...")
if showToast then
showToast("Camera settings")
end
end
-- Handle tap to focus
function onViewfinderTap(x, y)
print("[Camera] Focus at: " .. x .. ", " .. y)
-- Move focus indicator
local focus = camera_doc:GetElementById("focus-indicator")
if focus then
focus.style.left = x .. "px"
focus.style.top = y .. "px"
focus.style.opacity = "1"
if setTimeout then
setTimeout(function()
focus.style.opacity = "0.8"
end, 500)
end
end
end

View File

@@ -6,6 +6,7 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="camera.lua"></script>
<title>Camera</title>
<style>
.camera-screen {
@@ -40,9 +41,14 @@
background-color: rgba(255, 255, 255, 0.2);
}
.camera-btn:active {
background-color: rgba(255, 255, 255, 0.3);
}
.camera-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
/* Viewfinder */
@@ -169,10 +175,15 @@
justify-content: center;
}
.gallery-preview:hover {
background-color: #444444;
}
.gallery-preview img {
width: 28px;
height: 28px;
opacity: 0.7;
pointer-events: none;
}
.capture-btn {
@@ -203,6 +214,20 @@
background-color: #FFFFFF;
}
.capture-btn-video {
width: 24px;
height: 24px;
border-radius: 12px;
background-color: #F44336;
}
.capture-btn-stop {
width: 24px;
height: 24px;
border-radius: 4px;
background-color: #F44336;
}
.switch-camera-btn {
width: 48px;
height: 48px;
@@ -221,13 +246,12 @@
.switch-camera-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
/* Indicators */
.flash-indicator {
.indicator {
position: absolute;
top: 100px;
left: 16px;
background-color: rgba(0, 0, 0, 0.4);
padding: 6px 12px;
border-radius: 12px;
@@ -235,15 +259,30 @@
color: #FFFFFF;
}
.flash-indicator {
top: 100px;
left: 16px;
}
.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;
}
.recording-indicator {
top: 140px;
left: 50%;
transform: translateX(-50%);
display: none;
align-items: center;
gap: 8px;
}
.recording-dot {
width: 12px;
height: 12px;
border-radius: 6px;
background-color: #F44336;
}
/* Zoom control */
@@ -270,6 +309,10 @@
cursor: pointer;
}
.zoom-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.zoom-level {
font-size: 16px;
color: #FFD700;
@@ -279,7 +322,7 @@
}
</style>
</head>
<body class="app-screen camera-screen" onload="initLayout(document)">
<body class="app-screen camera-screen" onload="initLayout(document); initCamera(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>
@@ -296,13 +339,13 @@
<img src="../../icons/close.tga"/>
</div>
<div style="display: flex; gap: 12px;">
<div class="camera-btn">
<div class="camera-btn" onclick="toggleFlash()">
<img src="../../icons/flash.tga"/>
</div>
<div class="camera-btn">
<div class="camera-btn" onclick="toggleTimer()">
<img src="../../icons/timer.tga"/>
</div>
<div class="camera-btn">
<div class="camera-btn" onclick="openCameraSettings()">
<img src="../../icons/settings.tga"/>
</div>
</div>
@@ -311,7 +354,7 @@
<!-- Viewfinder Area -->
<div class="viewfinder-container">
<div class="viewfinder" id="camera-viewfinder">
<div class="viewfinder-placeholder">
<div class="viewfinder-placeholder" id="viewfinder-placeholder">
<div class="viewfinder-placeholder-icon">C</div>
<div>Camera Preview</div>
<div style="font-size: 14px; margin-top: 8px; color: #555555;">
@@ -328,39 +371,43 @@
</div>
<!-- Focus Indicator -->
<div class="focus-indicator"></div>
<div class="focus-indicator" id="focus-indicator"></div>
</div>
<!-- Indicators -->
<div class="flash-indicator">Flash: Auto</div>
<div class="timer-indicator">Timer: Off</div>
<div class="indicator flash-indicator" id="flash-indicator">Flash: Auto</div>
<div class="indicator timer-indicator" id="timer-indicator">Timer: Off</div>
<div class="indicator recording-indicator" id="recording-indicator">
<div class="recording-dot"></div>
<span id="recording-time">00:00</span>
</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 class="zoom-btn" onclick="zoomOut()">-</div>
<span class="zoom-level" id="zoom-level">1.0x</span>
<div class="zoom-btn" onclick="zoomIn()">+</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>
<span id="mode-night" class="camera-mode" onclick="switchMode('Night')">Night</span>
<span id="mode-portrait" class="camera-mode" onclick="switchMode('Portrait')">Portrait</span>
<span id="mode-photo" class="camera-mode active" onclick="switchMode('Photo')">Photo</span>
<span id="mode-video" class="camera-mode" onclick="switchMode('Video')">Video</span>
<span id="mode-more" class="camera-mode" onclick="switchMode('More')">More</span>
</div>
<!-- Bottom Bar -->
<div class="camera-bottom-bar">
<div class="gallery-preview">
<div class="gallery-preview" id="gallery-preview" onclick="openGallery()">
<img src="../../icons/gallery.tga"/>
</div>
<div class="capture-btn" id="capture-button">
<div class="capture-btn" id="capture-button" onclick="capture()">
<div class="capture-btn-inner"></div>
</div>
<div class="switch-camera-btn">
<div class="switch-camera-btn" onclick="switchCamera()">
<img src="../../icons/switch-camera.tga"/>
</div>
</div>

View File

@@ -0,0 +1,374 @@
-- contacts.lua - Contacts management functionality
-- Handles contact list, search, details, and actions
local contacts_doc = nil
local contacts_data = {}
local filtered_contacts = {}
local search_query = ""
local selected_contact = nil
-- Avatar colors for contacts
local avatar_colors = {
"#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3",
"#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#8BC34A",
"#CDDC39", "#FFC107", "#FF9800", "#FF5722", "#795548"
}
-- Get color for contact based on name
local function getAvatarColor(name)
local sum = 0
for i = 1, #name do
sum = sum + string.byte(name, i)
end
return avatar_colors[(sum % #avatar_colors) + 1]
end
-- Initialize contacts data
local function initContactsData()
contacts_data = {
{id = "1", name = "Alice Johnson", phone = "+1 555-0101", email = "alice@email.com", company = "Tech Corp"},
{id = "2", name = "Andrew Smith", phone = "+1 555-0102", email = "andrew@email.com", company = "Design Studio"},
{id = "3", name = "Bob Williams", phone = "+1 555-0201", email = "bob@email.com", company = ""},
{id = "4", name = "Carol Davis", phone = "+1 555-0301", email = "carol.d@email.com", company = "Marketing Inc"},
{id = "5", name = "Chris Miller", phone = "+1 555-0302", email = "", company = ""},
{id = "6", name = "David Brown", phone = "+1 555-0401", email = "david.b@email.com", company = "Finance LLC"},
{id = "7", name = "Emma Wilson", phone = "+1 555-0501", email = "emma@email.com", company = "Creative Agency"},
{id = "8", name = "Frank Garcia", phone = "+1 555-0601", email = "", company = ""},
{id = "9", name = "Grace Lee", phone = "+1 555-0701", email = "grace.lee@email.com", company = "Healthcare Plus"},
{id = "10", name = "Henry Taylor", phone = "+1 555-0801", email = "henry@email.com", company = ""},
{id = "11", name = "Isabella Martinez", phone = "+1 555-0901", email = "isabella@email.com", company = "Education Center"},
{id = "12", name = "John Doe", phone = "+1 555-1234", email = "john.doe@email.com", company = "Software Inc"},
{id = "13", name = "Kate Thompson", phone = "+1 555-1101", email = "", company = "Legal Partners"},
{id = "14", name = "Liam Anderson", phone = "+1 555-1201", email = "liam.a@email.com", company = ""},
{id = "15", name = "Mary Taylor", phone = "+1 555-0601", email = "mary@email.com", company = "Consulting Group"},
{id = "16", name = "Michael Lee", phone = "+1 555-0602", email = "michael.l@email.com", company = ""},
{id = "17", name = "Noah White", phone = "+1 555-1401", email = "noah@email.com", company = "Real Estate Co"},
{id = "18", name = "Olivia Harris", phone = "+1 555-1501", email = "", company = ""},
{id = "19", name = "Peter Clark", phone = "+1 555-1601", email = "peter.c@email.com", company = "Manufacturing Ltd"},
{id = "20", name = "Sarah Anderson", phone = "+1 555-0701", email = "sarah@email.com", company = "Media Group"},
}
-- Sort by name
table.sort(contacts_data, function(a, b)
return a.name:lower() < b.name:lower()
end)
filtered_contacts = contacts_data
end
-- Initialize contacts
function initContacts(doc)
print("[Contacts] Initializing...")
contacts_doc = doc
initContactsData()
renderContacts()
end
-- Render contacts list grouped by first letter
function renderContacts()
if not contacts_doc then return end
local container = contacts_doc:GetElementById("contacts-list")
if not container then return end
local html = ""
local current_letter = ""
for _, contact in ipairs(filtered_contacts) do
local first_letter = contact.name:sub(1, 1):upper()
-- Add letter header if new letter
if first_letter ~= current_letter then
current_letter = first_letter
html = html .. [[
<div class="contact-letter">]] .. first_letter .. [[</div>
]]
end
-- Get avatar color and initial
local color = getAvatarColor(contact.name)
local initial = contact.name:sub(1, 1):upper()
html = html .. [[
<div class="contact-item" onclick="selectContact(']] .. contact.id .. [[')">
<div class="contact-avatar" style="background-color: ]] .. color .. [[;">]] .. initial .. [[</div>
<div class="contact-info">
<div class="contact-name">]] .. contact.name .. [[</div>
<div class="contact-phone">]] .. contact.phone .. [[</div>
</div>
<div class="contact-call-btn" onclick="callContact(']] .. contact.id .. [['); event.stopPropagation();">
<img src="../../icons/phone.tga"/>
</div>
</div>
]]
end
if #filtered_contacts == 0 then
html = [[
<div style="text-align: center; padding: 40px; color: #888888;">
<div style="font-size: 18px;">No contacts found</div>
</div>
]]
end
container.inner_rml = html
end
-- Search contacts
function searchContacts(query)
print("[Contacts] Searching: " .. query)
search_query = query:lower()
if search_query == "" then
filtered_contacts = contacts_data
else
filtered_contacts = {}
for _, contact in ipairs(contacts_data) do
if contact.name:lower():find(search_query, 1, true) or
contact.phone:find(search_query, 1, true) or
(contact.email and contact.email:lower():find(search_query, 1, true)) then
table.insert(filtered_contacts, contact)
end
end
end
renderContacts()
end
-- Handle search input
function onSearchInput(element)
local query = element.value or ""
searchContacts(query)
end
-- Select a contact to view details
function selectContact(contact_id)
print("[Contacts] Selected contact: " .. contact_id)
-- Find contact by ID
for _, contact in ipairs(contacts_data) do
if contact.id == contact_id then
selected_contact = contact
break
end
end
if selected_contact then
showContactDetail()
end
end
-- Show contact detail view
function showContactDetail()
if not selected_contact or not contacts_doc then return end
-- Store contact info for detail screen
if mosis and mosis.state then
mosis.state.set("selected_contact", selected_contact)
end
-- Navigate to detail screen
if navigateTo then
navigateTo("contact_detail")
else
-- Show inline detail
showContactDetailInline()
end
end
-- Show contact detail inline (if navigation not available)
function showContactDetailInline()
if not contacts_doc then return end
local detail = contacts_doc:GetElementById("contact-detail")
local list = contacts_doc:GetElementById("contacts-list-container")
if detail and list then
list.style.display = "none"
detail.style.display = "flex"
renderContactDetail()
end
end
-- Render contact detail
function renderContactDetail()
if not selected_contact or not contacts_doc then return end
local color = getAvatarColor(selected_contact.name)
local initial = selected_contact.name:sub(1, 1):upper()
local detail_avatar = contacts_doc:GetElementById("detail-avatar")
local detail_name = contacts_doc:GetElementById("detail-name")
local detail_info = contacts_doc:GetElementById("detail-info")
if detail_avatar then
detail_avatar.style["background-color"] = color
detail_avatar.inner_rml = initial
end
if detail_name then
detail_name.inner_rml = selected_contact.name
end
if detail_info then
local html = ""
-- Phone
html = html .. [[
<div class="detail-row" onclick="callContact(']] .. selected_contact.id .. [[')">
<div class="detail-icon"><img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/></div>
<div class="detail-content">
<div class="detail-label">Phone</div>
<div class="detail-value">]] .. selected_contact.phone .. [[</div>
</div>
<div class="detail-action"><img src="../../icons/call_small.tga" style="width: 24px; height: 24px;"/></div>
</div>
]]
-- Email
if selected_contact.email and selected_contact.email ~= "" then
html = html .. [[
<div class="detail-row">
<div class="detail-icon"><img src="../../icons/email.tga" style="width: 24px; height: 24px;"/></div>
<div class="detail-content">
<div class="detail-label">Email</div>
<div class="detail-value">]] .. selected_contact.email .. [[</div>
</div>
</div>
]]
end
-- Company
if selected_contact.company and selected_contact.company ~= "" then
html = html .. [[
<div class="detail-row">
<div class="detail-icon"><img src="../../icons/work.tga" style="width: 24px; height: 24px;"/></div>
<div class="detail-content">
<div class="detail-label">Company</div>
<div class="detail-value">]] .. selected_contact.company .. [[</div>
</div>
</div>
]]
end
detail_info.inner_rml = html
end
end
-- Hide contact detail
function hideContactDetail()
if not contacts_doc then return end
local detail = contacts_doc:GetElementById("contact-detail")
local list = contacts_doc:GetElementById("contacts-list-container")
if detail and list then
detail.style.display = "none"
list.style.display = "flex"
end
selected_contact = nil
end
-- Call a contact
function callContact(contact_id)
print("[Contacts] Calling contact: " .. contact_id)
local contact = nil
for _, c in ipairs(contacts_data) do
if c.id == contact_id then
contact = c
break
end
end
if contact then
-- Store call info
if mosis and mosis.state then
mosis.state.set("current_call", {
number = contact.phone,
name = contact.name
})
end
-- Navigate to calling screen
if navigateTo then
navigateTo("calling")
else
if showToast then
showToast("Calling " .. contact.name)
end
end
end
end
-- Message a contact
function messageContact(contact_id)
print("[Contacts] Messaging contact: " .. contact_id)
local contact = nil
for _, c in ipairs(contacts_data) do
if c.id == contact_id then
contact = c
break
end
end
if contact then
if mosis and mosis.state then
mosis.state.set("chat_contact", {
name = contact.name,
phone = contact.phone
})
end
if navigateTo then
navigateTo("chat")
else
if showToast then
showToast("Message " .. contact.name)
end
end
end
end
-- Add new contact
function addContact()
print("[Contacts] Add new contact")
if navigateTo then
navigateTo("add_contact")
else
if showToast then
showToast("Add contact")
end
end
end
-- Edit contact
function editContact(contact_id)
print("[Contacts] Edit contact: " .. contact_id)
if showToast then
showToast("Edit contact")
end
end
-- Delete contact
function deleteContact(contact_id)
print("[Contacts] Delete contact: " .. contact_id)
-- Find and remove contact
for i, c in ipairs(contacts_data) do
if c.id == contact_id then
table.remove(contacts_data, i)
break
end
end
-- Re-filter and render
searchContacts(search_query)
hideContactDetail()
if showToast then
showToast("Contact deleted")
end
end

View File

@@ -6,6 +6,7 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="contacts.lua"></script>
<title>Contacts</title>
<style>
.contacts-list {
@@ -89,6 +90,123 @@
pointer-events: none;
}
/* Contact Detail View */
#contact-detail {
display: none;
flex-direction: column;
flex: 1;
}
.detail-header {
padding: 32px 16px;
text-align: center;
background-color: #1E1E1E;
}
.detail-avatar {
width: 96px;
height: 96px;
border-radius: 48px;
margin: 0 auto 16px auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
color: #000000;
}
.detail-name {
font-size: 28px;
font-weight: 500;
color: #FFFFFF;
}
.detail-actions {
display: flex;
justify-content: center;
gap: 32px;
padding: 20px;
background-color: #1E1E1E;
border-bottom: 1px solid #333333;
}
.detail-action-btn {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
padding: 8px;
}
.detail-action-btn:hover {
opacity: 0.8;
}
.detail-action-icon {
width: 48px;
height: 48px;
border-radius: 24px;
background-color: #BB86FC;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.detail-action-icon img {
width: 24px;
height: 24px;
pointer-events: none;
}
.detail-action-label {
font-size: 14px;
color: #FFFFFF;
}
.detail-info {
flex: 1;
padding: 16px;
}
.detail-row {
display: flex;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #333333;
cursor: pointer;
}
.detail-row:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.detail-icon {
width: 40px;
margin-right: 16px;
display: flex;
justify-content: center;
}
.detail-content {
flex: 1;
}
.detail-label {
font-size: 14px;
color: #888888;
}
.detail-value {
font-size: 18px;
color: #FFFFFF;
margin-top: 4px;
}
.detail-action {
padding: 8px;
}
/* Phone app bottom tabs */
.phone-tabs {
height: 72px;
@@ -123,9 +241,17 @@
.phone-tab span {
font-size: 14px;
}
/* Search style adjustments */
.search-input {
flex: 1;
background-color: transparent;
font-size: 18px;
color: #FFFFFF;
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="contacts">
<body class="app-screen" onload="initLayout(document); initContacts(document)" data-model="contacts">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
@@ -143,162 +269,67 @@
</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">
<div class="app-bar-action" onclick="addContact()">
<img src="../../icons/add.tga"/>
</div>
</div>
</div>
<!-- Contacts List Container -->
<div id="contacts-list-container" class="app-content" style="display: flex; flex-direction: column;">
<!-- 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"/>
<input class="search-input" type="text" placeholder="Search contacts" onchange="onSearchInput(this)"/>
</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 class="contacts-list" id="contacts-list">
<!-- Populated by Lua -->
</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>
<!-- Contact Detail View -->
<div id="contact-detail">
<div class="detail-header">
<div class="detail-avatar" id="detail-avatar">A</div>
<div class="detail-name" id="detail-name">Contact Name</div>
</div>
<div class="contact-call-btn">
<img src="../../icons/phone.tga"/>
<div class="detail-actions">
<div class="detail-action-btn" onclick="callContact(selected_contact and selected_contact.id or '')">
<div class="detail-action-icon">
<img src="../../icons/call_small.tga"/>
</div>
<span class="detail-action-label">Call</span>
</div>
<div class="detail-action-btn" onclick="messageContact(selected_contact and selected_contact.id or '')">
<div class="detail-action-icon" style="background-color: #03DAC6;">
<img src="../../icons/message.tga"/>
</div>
<span class="detail-action-label">Message</span>
</div>
<div class="detail-action-btn" onclick="editContact(selected_contact and selected_contact.id or '')">
<div class="detail-action-icon" style="background-color: #FF9800;">
<img src="../../icons/edit.tga"/>
</div>
<span class="detail-action-label">Edit</span>
</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 class="detail-info" id="detail-info">
<!-- Populated by Lua -->
</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 style="padding: 16px;">
<div class="btn btn-outlined" style="width: 100%; text-align: center;" onclick="hideContactDetail()">
Back to Contacts
</div>
</div>
</div>
<!-- FAB -->
<div class="btn-fab">
<div class="btn-fab" onclick="addContact()">
<img src="../../icons/add.tga" style="width: 32px; height: 32px;"/>
</div>
@@ -308,7 +339,7 @@
<img src="../../icons/dialpad.tga"/>
<span>Keypad</span>
</div>
<div class="phone-tab">
<div class="phone-tab" onclick="switchTab('recent')">
<img src="../../icons/history.tga"/>
<span>Recent</span>
</div>

View File

@@ -0,0 +1,227 @@
-- calling.lua - In-call screen functionality
-- Handles call state, duration timer, and call controls
local calling_doc = nil
local call_state = "connecting" -- connecting, active, ended
local call_start_time = 0
local call_duration = 0
local timer_id = nil
local is_muted = false
local is_speaker = false
local is_on_hold = false
-- Call info
local call_number = ""
local call_name = ""
-- Initialize calling screen
function initCalling(doc)
print("[Calling] Initializing...")
calling_doc = doc
-- Get call info from state or use defaults
if mosis and mosis.state then
local call_info = mosis.state.get("current_call")
if call_info then
call_number = call_info.number or ""
call_name = call_info.name or call_number
end
end
-- Fallback test data
if call_number == "" then
call_number = "+1 555-0101"
call_name = "Alice Johnson"
end
-- Update UI
updateCallInfo()
-- Simulate connection after delay
if setTimeout then
setTimeout(function()
setCallState("active")
end, 2000)
else
setCallState("active")
end
end
-- Update call info display
function updateCallInfo()
if not calling_doc then return end
local name_el = calling_doc:GetElementById("call-name")
local number_el = calling_doc:GetElementById("call-number")
local status_el = calling_doc:GetElementById("call-status")
local avatar_el = calling_doc:GetElementById("call-avatar")
if name_el then
name_el.inner_rml = call_name
end
if number_el then
number_el.inner_rml = call_number
end
if avatar_el then
-- Get first letter for avatar
local initial = call_name:sub(1, 1):upper()
avatar_el.inner_rml = initial
end
end
-- Set call state
function setCallState(state)
print("[Calling] State changed to: " .. state)
call_state = state
local status_el = calling_doc:GetElementById("call-status")
local timer_el = calling_doc:GetElementById("call-timer")
if state == "connecting" then
if status_el then
status_el.inner_rml = "Calling..."
end
if timer_el then
timer_el.style.display = "none"
end
elseif state == "active" then
if status_el then
status_el.inner_rml = "Connected"
end
if timer_el then
timer_el.style.display = "block"
end
-- Start duration timer
startCallTimer()
elseif state == "ended" then
if status_el then
status_el.inner_rml = "Call ended"
end
stopCallTimer()
-- Return to dialer after delay
if setTimeout then
setTimeout(function()
if goBack then
goBack()
end
end, 1500)
end
end
end
-- Start call duration timer
function startCallTimer()
call_start_time = os.time and os.time() or 0
call_duration = 0
if setInterval then
timer_id = setInterval(function()
call_duration = call_duration + 1
updateTimerDisplay()
end, 1000)
end
end
-- Stop call timer
function stopCallTimer()
if timer_id and clearInterval then
clearInterval(timer_id)
timer_id = nil
end
end
-- Update timer display
function updateTimerDisplay()
local timer_el = calling_doc:GetElementById("call-timer")
if timer_el then
local minutes = math.floor(call_duration / 60)
local seconds = call_duration % 60
timer_el.inner_rml = string.format("%02d:%02d", minutes, seconds)
end
end
-- Toggle mute
function toggleMute()
is_muted = not is_muted
print("[Calling] Mute: " .. tostring(is_muted))
local mute_btn = calling_doc:GetElementById("btn-mute")
if mute_btn then
if is_muted then
mute_btn:SetClass("active", true)
else
mute_btn:SetClass("active", false)
end
end
if showToast then
showToast(is_muted and "Muted" or "Unmuted")
end
end
-- Toggle speaker
function toggleSpeaker()
is_speaker = not is_speaker
print("[Calling] Speaker: " .. tostring(is_speaker))
local speaker_btn = calling_doc:GetElementById("btn-speaker")
if speaker_btn then
if is_speaker then
speaker_btn:SetClass("active", true)
else
speaker_btn:SetClass("active", false)
end
end
if showToast then
showToast(is_speaker and "Speaker on" or "Speaker off")
end
end
-- Toggle hold
function toggleHold()
is_on_hold = not is_on_hold
print("[Calling] Hold: " .. tostring(is_on_hold))
local hold_btn = calling_doc:GetElementById("btn-hold")
if hold_btn then
if is_on_hold then
hold_btn:SetClass("active", true)
else
hold_btn:SetClass("active", false)
end
end
local status_el = calling_doc:GetElementById("call-status")
if status_el then
if is_on_hold then
status_el.inner_rml = "On hold"
else
status_el.inner_rml = "Connected"
end
end
end
-- Show dialpad
function showDialpad()
print("[Calling] Show dialpad")
if showToast then
showToast("Dialpad")
end
end
-- Add call (conference)
function addCall()
print("[Calling] Add call")
if showToast then
showToast("Add call")
end
end
-- End call
function endCall()
print("[Calling] Ending call")
setCallState("ended")
end

View File

@@ -4,45 +4,30 @@
<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="calling.lua"></script>
<title>Calling</title>
<style>
.calling-screen {
width: 100%;
height: 100%;
background: linear-gradient(180deg, #1a237e 0%, #121212 100%);
background: linear-gradient(180deg, #1a237e 0%, #000000 100%);
background-color: #1a237e;
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-header {
padding-top: 60px;
text-align: center;
}
.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;
.call-avatar {
width: 120px;
height: 120px;
border-radius: 60px;
background-color: #BB86FC;
margin: 0 auto 24px auto;
display: flex;
align-items: center;
justify-content: center;
@@ -50,77 +35,157 @@
color: #000000;
}
.calling-actions {
position: absolute;
bottom: 120px;
display: flex;
gap: 48px;
.call-name {
font-size: 32px;
font-weight: 500;
color: #FFFFFF;
margin-bottom: 8px;
}
.call-action-btn {
width: 64px;
height: 64px;
border-radius: 32px;
background-color: rgba(255, 255, 255, 0.1);
.call-number {
font-size: 18px;
color: #B3B3B3;
margin-bottom: 16px;
}
.call-status {
font-size: 18px;
color: #4CAF50;
margin-bottom: 8px;
}
.call-timer {
font-size: 24px;
color: #FFFFFF;
font-weight: 300;
display: none;
}
.calling-content {
flex: 1;
}
.call-controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 40px;
gap: 32px;
max-width: 320px;
}
.call-control-btn {
width: 72px;
height: 72px;
border-radius: 36px;
background-color: rgba(255, 255, 255, 0.15);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.call-action-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
.call-control-btn:hover {
background-color: rgba(255, 255, 255, 0.25);
}
.call-action-btn img {
.call-control-btn.active {
background-color: #FFFFFF;
}
.call-control-btn img {
width: 32px;
height: 32px;
pointer-events: none;
}
.call-control-label {
font-size: 12px;
color: #FFFFFF;
margin-top: 8px;
text-align: center;
}
.call-control-btn.active .call-control-label {
color: #000000;
}
.end-call-container {
padding: 40px;
}
.end-call-btn {
position: absolute;
bottom: 40px;
width: 72px;
height: 72px;
border-radius: 36px;
width: 80px;
height: 80px;
border-radius: 40px;
background-color: #F44336;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin: 0 auto;
}
.end-call-btn:hover {
background-color: #E53935;
transform: scale(1.05);
}
.end-call-btn:active {
background-color: #C62828;
transform: scale(0.95);
}
.end-call-btn img {
width: 32px;
height: 32px;
width: 40px;
height: 40px;
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>
<body class="calling-screen" onload="initCalling(document)">
<!-- Caller Info -->
<div class="calling-header">
<div class="call-avatar" id="call-avatar">A</div>
<div class="call-name" id="call-name">Alice Johnson</div>
<div class="call-number" id="call-number">+1 555-0101</div>
<div class="call-status" id="call-status">Calling...</div>
<div class="call-timer" id="call-timer">00:00</div>
</div>
<div class="calling-avatar" id="calling-avatar">?</div>
<!-- Spacer -->
<div class="calling-content"></div>
<div class="calling-actions">
<div class="call-action-btn">
<!-- Call Controls -->
<div class="call-controls">
<div id="btn-mute" class="call-control-btn" onclick="toggleMute()">
<img src="../../icons/mic_off.tga"/>
<span class="call-control-label">Mute</span>
</div>
<div id="btn-keypad" class="call-control-btn" onclick="showDialpad()">
<img src="../../icons/dialpad.tga"/>
<span class="call-control-label">Keypad</span>
</div>
<div class="call-action-btn">
<img src="../../icons/contacts.tga"/>
<div id="btn-speaker" class="call-control-btn" onclick="toggleSpeaker()">
<img src="../../icons/volume.tga"/>
<span class="call-control-label">Speaker</span>
</div>
<div id="btn-add" class="call-control-btn" onclick="addCall()">
<img src="../../icons/add.tga"/>
<span class="call-control-label">Add call</span>
</div>
<div id="btn-hold" class="call-control-btn" onclick="toggleHold()">
<img src="../../icons/pause.tga"/>
<span class="call-control-label">Hold</span>
</div>
</div>
<!-- End Call Button -->
<div class="end-call-container">
<div class="end-call-btn" onclick="endCall()">
<img src="../../icons/phone.tga"/>
<img src="../../icons/call_end.tga"/>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,235 @@
-- dialer.lua - Phone dialer functionality
-- Handles dial pad input, call management, and call history
local dialer_doc = nil
local dial_number = ""
local call_history = {}
local current_tab = "keypad" -- keypad, recent, contacts
-- Sample call history data
local function initCallHistory()
call_history = {
{name = "Alice Johnson", number = "+1 555-0101", type = "incoming", time = "2:34 PM", duration = "5:23"},
{name = "Bob Williams", number = "+1 555-0201", type = "outgoing", time = "1:15 PM", duration = "2:45"},
{name = "Carol Davis", number = "+1 555-0301", type = "missed", time = "Yesterday", duration = nil},
{name = "David Brown", number = "+1 555-0401", type = "incoming", time = "Yesterday", duration = "12:30"},
{name = "Emma Wilson", number = "+1 555-0501", type = "outgoing", time = "Mon", duration = "3:15"},
{name = "+1 555-9999", number = "+1 555-9999", type = "missed", time = "Mon", duration = nil},
{name = "John Doe", number = "+1 555-1234", type = "incoming", time = "Sun", duration = "8:42"},
}
end
-- Initialize dialer
function initDialer(doc)
print("[Dialer] Initializing...")
dialer_doc = doc
dial_number = ""
initCallHistory()
updateDialDisplay()
end
-- Update the dial display
function updateDialDisplay()
if not dialer_doc then return end
local display = dialer_doc:GetElementById("dial-display")
if display then
if dial_number == "" then
display.inner_rml = '<span style="color: #666666;">Enter number</span>'
else
-- Format number for display
local formatted = formatPhoneNumber(dial_number)
display.inner_rml = formatted
end
end
end
-- Format phone number for display
function formatPhoneNumber(number)
local len = #number
if len <= 3 then
return number
elseif len <= 6 then
return number:sub(1,3) .. "-" .. number:sub(4)
elseif len <= 10 then
return "(" .. number:sub(1,3) .. ") " .. number:sub(4,6) .. "-" .. number:sub(7)
else
return "+1 (" .. number:sub(1,3) .. ") " .. number:sub(4,6) .. "-" .. number:sub(7,10)
end
end
-- Handle dial key press
function dial_press(key)
print("[Dialer] Key pressed: " .. key)
if #dial_number < 15 then
dial_number = dial_number .. key
updateDialDisplay()
-- Play haptic/sound feedback if available
if mosis and mosis.haptic then
mosis.haptic.vibrate(10)
end
end
end
-- Handle backspace
function dial_backspace()
if #dial_number > 0 then
dial_number = dial_number:sub(1, -2)
updateDialDisplay()
end
end
-- Clear dial number
function dial_clear()
dial_number = ""
updateDialDisplay()
end
-- Make a call
function make_call()
if dial_number == "" then
print("[Dialer] Cannot call: no number entered")
if showToast then
showToast("Enter a number to call")
end
return
end
print("[Dialer] Calling: " .. dial_number)
-- Add to call history
table.insert(call_history, 1, {
name = dial_number,
number = dial_number,
type = "outgoing",
time = "Just now",
duration = nil
})
-- Navigate to calling screen
if navigateTo then
-- Store call info for the calling screen
if mosis and mosis.state then
mosis.state.set("current_call", {
number = dial_number,
name = getContactName(dial_number),
start_time = os.time and os.time() or 0
})
end
navigateTo("calling")
else
-- Fallback: load calling screen directly
local calling_path = dialer_doc:GetSourceURL():gsub("dialer.rml", "calling.rml")
if mosis and mosis.loadDocument then
mosis.loadDocument(calling_path)
end
end
end
-- Get contact name by number (returns number if not found)
function getContactName(number)
for _, call in ipairs(call_history) do
if call.number == number and call.name ~= number then
return call.name
end
end
return number
end
-- Switch tabs
function switchTab(tab_name)
print("[Dialer] Switching to tab: " .. tab_name)
current_tab = tab_name
-- Update tab UI
local tabs = {"keypad", "recent", "contacts"}
for _, tab in ipairs(tabs) do
local tab_el = dialer_doc:GetElementById("tab-" .. tab)
if tab_el then
if tab == tab_name then
tab_el:SetClass("active", true)
else
tab_el:SetClass("active", false)
end
end
end
-- Show/hide content
local keypad_content = dialer_doc:GetElementById("keypad-content")
local recent_content = dialer_doc:GetElementById("recent-content")
if keypad_content then
keypad_content.style.display = (tab_name == "keypad") and "flex" or "none"
end
if recent_content then
recent_content.style.display = (tab_name == "recent") and "block" or "none"
end
-- Render recent calls if switching to that tab
if tab_name == "recent" then
renderCallHistory()
end
end
-- Render call history
function renderCallHistory()
local container = dialer_doc:GetElementById("recent-list")
if not container then return end
local html = ""
for _, call in ipairs(call_history) do
local icon_color = "#4CAF50" -- incoming = green
local icon = "phone.tga"
if call.type == "outgoing" then
icon_color = "#2196F3" -- blue
icon = "call_made.tga"
elseif call.type == "missed" then
icon_color = "#F44336" -- red
icon = "call_missed.tga"
end
local duration_text = call.duration or "Missed"
html = html .. [[
<div class="call-history-item" onclick="callNumber(']] .. call.number .. [[')">
<div class="call-history-icon" style="background-color: ]] .. icon_color .. [[;">
<img src="../../icons/]] .. icon .. [[" style="width: 24px; height: 24px;"/>
</div>
<div class="call-history-info">
<div class="call-history-name">]] .. call.name .. [[</div>
<div class="call-history-meta">]] .. call.type .. " - " .. call.time .. [[</div>
</div>
<div class="call-history-time">]] .. duration_text .. [[</div>
</div>
]]
end
container.inner_rml = html
end
-- Call a number from history
function callNumber(number)
dial_number = number:gsub("[^%d+]", "") -- Remove non-digit chars except +
updateDialDisplay()
switchTab("keypad")
make_call()
end
-- Long press on 0 for +
function dial_long_press_zero()
if dial_number == "" or dial_number:sub(-1) ~= "0" then
dial_press("+")
else
-- Replace last 0 with +
dial_number = dial_number:sub(1, -2) .. "+"
updateDialDisplay()
end
end
-- Long press on * for pause
function dial_long_press_star()
dial_press(",") -- Comma is standard pause character
end

View File

@@ -6,6 +6,7 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="dialer.lua"></script>
<title>Phone</title>
<style>
.dialer-content {
@@ -14,6 +15,61 @@
flex-direction: column;
}
/* Keypad content */
#keypad-content {
flex: 1;
display: flex;
flex-direction: column;
}
/* Recent calls content */
#recent-content {
flex: 1;
display: none;
overflow: auto;
}
.call-history-item {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
}
.call-history-item:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.call-history-icon {
width: 40px;
height: 40px;
border-radius: 20px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.call-history-info {
flex: 1;
}
.call-history-name {
font-size: 16px;
color: #FFFFFF;
}
.call-history-meta {
font-size: 14px;
color: #888888;
margin-top: 2px;
}
.call-history-time {
font-size: 14px;
color: #888888;
}
/* Phone app bottom tabs */
.phone-tabs {
height: 72px;
@@ -50,7 +106,7 @@
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="phone">
<body class="app-screen" onload="initLayout(document); initDialer(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
@@ -71,56 +127,60 @@
<!-- Dialer Content -->
<div class="dialer-content">
<!-- Keypad View -->
<div id="keypad-content">
<!-- Dial Display -->
<div class="dial-display">{{ dial_number }}</div>
<div class="dial-display" id="dial-display">
<span style="color: #666666;">Enter number</span>
</div>
<!-- Dial Pad -->
<div class="dial-pad">
<div class="dial-key" data-event-click="dial_press('1')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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('*')">
<div class="dial-key" onclick="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')">
<div class="dial-key" onclick="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('#')">
<div class="dial-key" onclick="dial_press('#')">
<span class="dial-key-number">#</span>
<span class="dial-key-letters"></span>
</div>
@@ -129,26 +189,34 @@
<!-- Call Actions -->
<div class="dial-actions">
<div style="width: 56px;"></div>
<div class="dial-call-btn" data-event-click="make_call()">
<div class="dial-call-btn" onclick="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;">
<div class="btn-icon" onclick="dial_backspace()" style="width: 56px; height: 56px;">
<img src="../../icons/backspace.tga" style="width: 32px; height: 32px; pointer-events: none;"/>
</div>
</div>
</div>
<!-- Recent Calls View -->
<div id="recent-content">
<div id="recent-list">
<!-- Populated by Lua -->
</div>
</div>
</div>
<!-- Phone App Bottom Tabs -->
<div class="phone-tabs">
<div class="phone-tab active">
<div id="tab-keypad" class="phone-tab active" onclick="switchTab('keypad')">
<img src="../../icons/dialpad.tga"/>
<span>Keypad</span>
</div>
<div class="phone-tab">
<div id="tab-recent" class="phone-tab" onclick="switchTab('recent')">
<img src="../../icons/history.tga"/>
<span>Recent</span>
</div>
<div class="phone-tab" onclick="navigateTo('contacts')">
<div id="tab-contacts" class="phone-tab" onclick="navigateTo('contacts')">
<img src="../../icons/contacts.tga"/>
<span>Contacts</span>
</div>

View File

@@ -0,0 +1,398 @@
-- messages.lua - Messages app functionality
-- Handles conversation list and individual chats
local messages_doc = nil
local conversations = {}
local current_conversation = nil
local current_messages = {}
-- Avatar colors
local avatar_colors = {
"#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3",
"#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#FF9800"
}
local function getAvatarColor(name)
local sum = 0
for i = 1, #name do
sum = sum + string.byte(name, i)
end
return avatar_colors[(sum % #avatar_colors) + 1]
end
-- Initialize conversations data
local function initConversationsData()
conversations = {
{
id = "1",
name = "Alice Johnson",
phone = "+1 555-0101",
last_message = "Hey! Are you coming to the party tonight?",
time = "2:34 PM",
unread = 2,
messages = {
{sender = "them", text = "Hey!", time = "2:30 PM"},
{sender = "them", text = "What are you up to?", time = "2:31 PM"},
{sender = "me", text = "Not much, just working", time = "2:32 PM"},
{sender = "them", text = "Cool! There's a party at Mike's tonight", time = "2:33 PM"},
{sender = "them", text = "Hey! Are you coming to the party tonight?", time = "2:34 PM"},
}
},
{
id = "2",
name = "Bob Williams",
phone = "+1 555-0201",
last_message = "Thanks for the help yesterday!",
time = "1:15 PM",
unread = 0,
messages = {
{sender = "them", text = "Hey, can you help me with something?", time = "Yesterday"},
{sender = "me", text = "Sure, what do you need?", time = "Yesterday"},
{sender = "them", text = "I need help moving some furniture", time = "Yesterday"},
{sender = "me", text = "No problem, I'll be there at 2", time = "Yesterday"},
{sender = "them", text = "Thanks for the help yesterday!", time = "1:15 PM"},
}
},
{
id = "3",
name = "Carol Davis",
phone = "+1 555-0301",
last_message = "The meeting has been rescheduled to Friday",
time = "Yesterday",
unread = 0,
messages = {
{sender = "them", text = "Hi, are you free for a meeting tomorrow?", time = "Monday"},
{sender = "me", text = "Let me check my calendar", time = "Monday"},
{sender = "me", text = "Yes, I'm free at 3pm", time = "Monday"},
{sender = "them", text = "The meeting has been rescheduled to Friday", time = "Yesterday"},
}
},
{
id = "4",
name = "David Brown",
phone = "+1 555-0401",
last_message = "Can you send me the files?",
time = "Yesterday",
unread = 1,
messages = {
{sender = "them", text = "Hey, do you have the project files?", time = "Yesterday"},
{sender = "me", text = "Which ones?", time = "Yesterday"},
{sender = "them", text = "Can you send me the files?", time = "Yesterday"},
}
},
{
id = "5",
name = "Emma Wilson",
phone = "+1 555-0501",
last_message = "See you at the coffee shop!",
time = "Mon",
unread = 0,
messages = {
{sender = "me", text = "Want to grab coffee later?", time = "Mon"},
{sender = "them", text = "Sure! What time?", time = "Mon"},
{sender = "me", text = "How about 4pm at the usual place?", time = "Mon"},
{sender = "them", text = "See you at the coffee shop!", time = "Mon"},
}
},
{
id = "6",
name = "Frank Miller",
phone = "+1 555-0601",
last_message = "Great game last night!",
time = "Sun",
unread = 0,
messages = {
{sender = "them", text = "Did you watch the game?", time = "Sun"},
{sender = "me", text = "Yes! It was amazing!", time = "Sun"},
{sender = "them", text = "Great game last night!", time = "Sun"},
}
},
{
id = "7",
name = "Grace Lee",
phone = "+1 555-0701",
last_message = "Happy birthday! :)",
time = "Sat",
unread = 0,
messages = {
{sender = "them", text = "Happy birthday! :)", time = "Sat"},
{sender = "me", text = "Thank you so much! :)", time = "Sat"},
}
},
}
end
-- Initialize messages app
function initMessages(doc)
print("[Messages] Initializing...")
messages_doc = doc
initConversationsData()
renderConversations()
end
-- Render conversation list
function renderConversations()
if not messages_doc then return end
local container = messages_doc:GetElementById("conversations-list")
if not container then return end
local html = ""
for _, conv in ipairs(conversations) do
local color = getAvatarColor(conv.name)
local initial = conv.name:sub(1, 1):upper()
local unread_badge = ""
if conv.unread > 0 then
unread_badge = [[<div class="conversation-unread">]] .. conv.unread .. [[</div>]]
end
html = html .. [[
<div class="conversation-item" onclick="openConversation(']] .. conv.id .. [[')">
<div class="conversation-avatar" style="background-color: ]] .. color .. [[;">]] .. initial .. [[</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">]] .. conv.name .. [[</span>
<span class="conversation-time">]] .. conv.time .. [[</span>
</div>
<div class="conversation-preview">]] .. conv.last_message .. [[</div>
</div>
]] .. unread_badge .. [[
</div>
]]
end
container.inner_rml = html
end
-- Open a conversation
function openConversation(conv_id)
print("[Messages] Opening conversation: " .. conv_id)
-- Find conversation
for _, conv in ipairs(conversations) do
if conv.id == conv_id then
current_conversation = conv
current_messages = conv.messages
conv.unread = 0 -- Mark as read
break
end
end
if current_conversation then
-- Store for chat screen
if mosis and mosis.state then
mosis.state.set("current_chat", {
id = current_conversation.id,
name = current_conversation.name,
phone = current_conversation.phone
})
end
-- Navigate to chat
if navigateTo then
navigateTo("chat")
else
-- Inline chat view
showChatInline()
end
end
end
-- Show chat inline
function showChatInline()
if not messages_doc then return end
local list = messages_doc:GetElementById("conversations-container")
local chat = messages_doc:GetElementById("chat-container")
if list and chat then
list.style.display = "none"
chat.style.display = "flex"
renderChat()
end
end
-- Hide chat and return to list
function hideChat()
if not messages_doc then return end
local list = messages_doc:GetElementById("conversations-container")
local chat = messages_doc:GetElementById("chat-container")
if list and chat then
chat.style.display = "none"
list.style.display = "flex"
renderConversations() -- Refresh to update unread counts
end
current_conversation = nil
end
-- Render chat messages
function renderChat()
if not messages_doc or not current_conversation then return end
-- Update header
local name_el = messages_doc:GetElementById("chat-name")
local avatar_el = messages_doc:GetElementById("chat-avatar")
if name_el then
name_el.inner_rml = current_conversation.name
end
if avatar_el then
local color = getAvatarColor(current_conversation.name)
local initial = current_conversation.name:sub(1, 1):upper()
avatar_el.style["background-color"] = color
avatar_el.inner_rml = initial
end
-- Render messages
local container = messages_doc:GetElementById("chat-messages")
if not container then return end
local html = ""
for _, msg in ipairs(current_messages) do
local class = msg.sender == "me" and "message-sent" or "message-received"
html = html .. [[
<div class="message-bubble ]] .. class .. [[">]] .. msg.text .. [[</div>
]]
end
container.inner_rml = html
-- Scroll to bottom
-- Note: RmlUi may need specific handling for scroll
end
-- Send a message
function sendMessage()
if not messages_doc or not current_conversation then return end
local input = messages_doc:GetElementById("message-input")
if not input then return end
local text = input.value or ""
if text == "" then return end
print("[Messages] Sending: " .. text)
-- Add message to current conversation
table.insert(current_messages, {
sender = "me",
text = text,
time = "Just now"
})
-- Update conversation preview
current_conversation.last_message = text
current_conversation.time = "Just now"
-- Clear input
input.value = ""
-- Re-render chat
renderChat()
-- Simulate reply after delay
if setTimeout then
setTimeout(function()
simulateReply()
end, 2000 + math.random(1000, 3000))
end
end
-- Simulate a reply
function simulateReply()
if not current_conversation then return end
local replies = {
"That's great!",
"I see",
"Sounds good!",
"Let me think about it",
"Sure thing!",
"OK!",
"Thanks!",
"Got it",
"Nice!",
"Interesting..."
}
local reply = replies[math.random(#replies)]
table.insert(current_messages, {
sender = "them",
text = reply,
time = "Just now"
})
current_conversation.last_message = reply
current_conversation.time = "Just now"
renderChat()
end
-- Handle input keypress (for Enter to send)
function onMessageKeypress(event)
if event.key == "Return" or event.key == "Enter" then
sendMessage()
return true
end
return false
end
-- Start new conversation
function newConversation()
print("[Messages] New conversation")
if showToast then
showToast("New message")
end
end
-- Search conversations
function searchConversations(query)
print("[Messages] Searching: " .. query)
-- TODO: Implement search filtering
end
-- Delete conversation
function deleteConversation(conv_id)
print("[Messages] Deleting conversation: " .. conv_id)
for i, conv in ipairs(conversations) do
if conv.id == conv_id then
table.remove(conversations, i)
break
end
end
renderConversations()
if showToast then
showToast("Conversation deleted")
end
end
-- Call contact from chat
function callFromChat()
if not current_conversation then return end
print("[Messages] Calling from chat: " .. current_conversation.name)
if mosis and mosis.state then
mosis.state.set("current_call", {
number = current_conversation.phone,
name = current_conversation.name
})
end
if navigateTo then
navigateTo("calling")
else
if showToast then
showToast("Calling " .. current_conversation.name)
end
end
end

View File

@@ -6,6 +6,7 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="messages.lua"></script>
<title>Messages</title>
<style>
.conversations-list {
@@ -84,9 +85,131 @@
justify-content: center;
margin-left: 8px;
}
/* Chat View */
#chat-container {
display: none;
flex-direction: column;
flex: 1;
}
.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: 18px;
font-weight: 500;
color: #FFFFFF;
}
.chat-header-status {
font-size: 14px;
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;
}
.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-input:hover {
background-color: #3D3D3D;
}
.chat-input:focus {
background-color: #353535;
}
.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:active {
background-color: #7C44DC;
}
.chat-send-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)" data-model="messages">
<body class="app-screen" onload="initLayout(document); initMessages(document)" data-model="messages">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
@@ -97,6 +220,8 @@
</div>
</div>
<!-- Conversations List Container -->
<div id="conversations-container" style="display: flex; flex-direction: column; flex: 1;">
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
@@ -112,97 +237,13 @@
<!-- 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 class="conversations-list" id="conversations-list">
<!-- Populated by Lua -->
</div>
</div>
<!-- FAB -->
<div class="btn-fab">
<div class="btn-fab" onclick="newConversation()">
<img src="../../icons/add.tga" style="width: 32px; height: 32px;"/>
</div>
@@ -216,5 +257,43 @@
<img src="../../icons/menu.tga"/>
</div>
</div>
</div>
<!-- Chat Container -->
<div id="chat-container">
<!-- Chat Header -->
<div class="app-bar">
<div class="app-bar-back" onclick="hideChat()">
<img src="../../icons/back.tga"/>
</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">Contact</div>
<div class="chat-header-status">Online</div>
</div>
<div class="btn-icon" onclick="callFromChat()">
<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">
<!-- Populated by Lua -->
</div>
<!-- Input Bar -->
<div class="chat-input-bar">
<div class="btn-icon" style="width: 48px; height: 48px;">
<img src="../../icons/add.tga" style="width: 28px; height: 28px;"/>
</div>
<input class="chat-input" type="text" placeholder="Type a message..." id="message-input"/>
<div class="chat-send-btn" onclick="sendMessage()">
<img src="../../icons/send.tga"/>
</div>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,388 @@
-- music.lua - Music player functionality
-- Handles playback, playlists, library, and now playing
local music_doc = nil
-- Player state
local player_state = {
is_playing = false,
is_shuffled = false,
repeat_mode = "off", -- off, all, one
current_time = 0,
duration = 234, -- 3:54
volume = 80
}
-- Current track
local current_track = {
id = "1",
title = "Midnight City",
artist = "M83",
album = "Hurry Up, We're Dreaming",
duration = 234,
art_color = "#667eea"
}
-- Playlists
local playlists = {
{id = "liked", name = "Liked Songs", count = 127, color = "#dc2626"},
{id = "daily1", name = "Daily Mix 1", count = 50, color = "#667eea"},
{id = "release", name = "Release Radar", count = 30, color = "#16a34a"},
{id = "chill", name = "Chill Vibes", count = 45, color = "#f093fb"},
{id = "workout", name = "Workout Mix", count = 35, color = "#2563eb"},
{id = "focus", name = "Focus Flow", count = 40, color = "#4facfe"}
}
-- Recently played
local recently_played = {
{id = "pop", name = "Pop Hits", type = "Playlist", color = "#43e97b"},
{id = "electronic", name = "Electronic", type = "Playlist", color = "#fa709a"},
{id = "jazz", name = "Jazz Classics", type = "Playlist", color = "#667eea"},
{id = "rock", name = "Rock Legends", type = "Playlist", color = "#f093fb"}
}
-- Song queue
local queue = {
{id = "1", title = "Midnight City", artist = "M83", duration = 234},
{id = "2", title = "Intro", artist = "The xx", duration = 128},
{id = "3", title = "Retrograde", artist = "James Blake", duration = 233},
{id = "4", title = "Tame Impala", artist = "The Less I Know The Better", duration = 218},
{id = "5", title = "Redbone", artist = "Childish Gambino", duration = 327}
}
local current_queue_index = 1
local timer_id = nil
-- Initialize music app
function initMusic(doc)
print("[Music] Initializing...")
music_doc = doc
updateNowPlaying()
updateMiniPlayer()
renderPlaylists()
renderRecentlyPlayed()
end
-- Format time (seconds to mm:ss)
local function formatTime(seconds)
local mins = math.floor(seconds / 60)
local secs = seconds % 60
return string.format("%d:%02d", mins, secs)
end
-- Update now playing display
function updateNowPlaying()
if not music_doc then return end
local title = music_doc:GetElementById("now-playing-title")
local artist = music_doc:GetElementById("now-playing-artist")
if title then
title.inner_rml = current_track.title
end
if artist then
artist.inner_rml = current_track.artist
end
end
-- Update mini player
function updateMiniPlayer()
if not music_doc then return end
local title = music_doc:GetElementById("mini-player-title")
local artist = music_doc:GetElementById("mini-player-artist")
local art = music_doc:GetElementById("mini-player-art")
local play_btn = music_doc:GetElementById("mini-play-btn")
if title then
title.inner_rml = current_track.title
end
if artist then
artist.inner_rml = current_track.artist
end
if art then
art.style["background-color"] = current_track.art_color
art.inner_rml = current_track.title:sub(1,1):upper()
end
if play_btn then
local icon = player_state.is_playing and "pause.tga" or "play.tga"
play_btn.inner_rml = [[<img src="../../icons/]] .. icon .. [[" style="width: 28px; height: 28px;"/>]]
end
end
-- Toggle play/pause
function togglePlay()
player_state.is_playing = not player_state.is_playing
print("[Music] " .. (player_state.is_playing and "Playing" or "Paused"))
if player_state.is_playing then
startPlaybackTimer()
else
stopPlaybackTimer()
end
updateMiniPlayer()
updatePlayButton()
end
-- Start playback timer
function startPlaybackTimer()
if setInterval then
timer_id = setInterval(function()
if player_state.is_playing then
player_state.current_time = player_state.current_time + 1
if player_state.current_time >= current_track.duration then
nextTrack()
end
updateProgress()
end
end, 1000)
end
end
-- Stop playback timer
function stopPlaybackTimer()
if timer_id and clearInterval then
clearInterval(timer_id)
timer_id = nil
end
end
-- Update play button
function updatePlayButton()
if not music_doc then return end
local btn = music_doc:GetElementById("play-btn")
if btn then
local icon = player_state.is_playing and "pause.tga" or "play.tga"
btn.inner_rml = [[<img src="../../icons/]] .. icon .. [[" style="width: 48px; height: 48px;"/>]]
end
end
-- Update progress display
function updateProgress()
if not music_doc then return end
local current = music_doc:GetElementById("current-time")
local total = music_doc:GetElementById("total-time")
local progress = music_doc:GetElementById("progress-bar")
if current then
current.inner_rml = formatTime(player_state.current_time)
end
if total then
total.inner_rml = formatTime(current_track.duration)
end
if progress then
local percent = (player_state.current_time / current_track.duration) * 100
progress.style.width = percent .. "%"
end
end
-- Next track
function nextTrack()
print("[Music] Next track")
if player_state.is_shuffled then
current_queue_index = math.random(1, #queue)
else
current_queue_index = current_queue_index + 1
if current_queue_index > #queue then
if player_state.repeat_mode == "all" then
current_queue_index = 1
else
current_queue_index = #queue
player_state.is_playing = false
stopPlaybackTimer()
end
end
end
loadTrack(queue[current_queue_index])
end
-- Previous track
function previousTrack()
print("[Music] Previous track")
if player_state.current_time > 3 then
-- Restart current track
player_state.current_time = 0
else
current_queue_index = current_queue_index - 1
if current_queue_index < 1 then
current_queue_index = 1
end
loadTrack(queue[current_queue_index])
end
updateProgress()
end
-- Load a track
function loadTrack(track)
current_track.id = track.id
current_track.title = track.title
current_track.artist = track.artist
current_track.duration = track.duration
player_state.current_time = 0
updateNowPlaying()
updateMiniPlayer()
updateProgress()
print("[Music] Now playing: " .. track.title .. " - " .. track.artist)
if showToast then
showToast("Now playing: " .. track.title)
end
end
-- Toggle shuffle
function toggleShuffle()
player_state.is_shuffled = not player_state.is_shuffled
print("[Music] Shuffle: " .. tostring(player_state.is_shuffled))
local btn = music_doc:GetElementById("shuffle-btn")
if btn then
if player_state.is_shuffled then
btn:SetClass("active", true)
else
btn:SetClass("active", false)
end
end
if showToast then
showToast(player_state.is_shuffled and "Shuffle on" or "Shuffle off")
end
end
-- Toggle repeat
function toggleRepeat()
if player_state.repeat_mode == "off" then
player_state.repeat_mode = "all"
elseif player_state.repeat_mode == "all" then
player_state.repeat_mode = "one"
else
player_state.repeat_mode = "off"
end
print("[Music] Repeat: " .. player_state.repeat_mode)
local btn = music_doc:GetElementById("repeat-btn")
if btn then
if player_state.repeat_mode ~= "off" then
btn:SetClass("active", true)
else
btn:SetClass("active", false)
end
end
if showToast then
local msg = "Repeat: " .. player_state.repeat_mode
showToast(msg)
end
end
-- Toggle like
function toggleLike()
print("[Music] Toggle like")
if showToast then
showToast("Added to Liked Songs")
end
end
-- Render playlists
function renderPlaylists()
if not music_doc then return end
local container = music_doc:GetElementById("quick-access")
if not container then return end
local html = ""
for i, pl in ipairs(playlists) do
if i <= 6 then
local initial = pl.name:sub(1,1):upper()
html = html .. [[
<div class="quick-card" onclick="openPlaylist(']] .. pl.id .. [[')">
<div class="quick-card-art" style="background-color: ]] .. pl.color .. [[;">]] .. initial .. [[</div>
<span class="quick-card-title">]] .. pl.name .. [[</span>
</div>
]]
end
end
container.inner_rml = html
end
-- Render recently played
function renderRecentlyPlayed()
if not music_doc then return end
local container = music_doc:GetElementById("recent-row")
if not container then return end
local html = ""
for _, item in ipairs(recently_played) do
local initial = item.name:sub(1,1):upper()
html = html .. [[
<div class="recent-item" onclick="openPlaylist(']] .. item.id .. [[')">
<div class="recent-art" style="background-color: ]] .. item.color .. [[;">]] .. initial .. [[</div>
<div class="recent-title">]] .. item.name .. [[</div>
<div class="recent-subtitle">]] .. item.type .. [[</div>
</div>
]]
end
container.inner_rml = html
end
-- Open playlist
function openPlaylist(playlist_id)
print("[Music] Opening playlist: " .. playlist_id)
if navigateTo then
navigateTo("playlist_" .. playlist_id)
else
if showToast then
showToast("Playlist: " .. playlist_id)
end
end
end
-- Open now playing
function openNowPlaying()
print("[Music] Opening now playing...")
if navigateTo then
navigateTo("now_playing")
end
end
-- Open search
function openSearch()
print("[Music] Opening search...")
if navigateTo then
navigateTo("music_search")
else
if showToast then
showToast("Search music")
end
end
end
-- Open library
function openLibrary()
print("[Music] Opening library...")
if navigateTo then
navigateTo("music_library")
else
if showToast then
showToast("Your library")
end
end
end
-- Seek to position (0-1)
function seekTo(position)
player_state.current_time = math.floor(position * current_track.duration)
updateProgress()
end

View File

@@ -6,20 +6,30 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="music.lua"></script>
<title>Music</title>
<style>
.music-content {
flex: 1;
overflow: auto;
padding-bottom: 140px;
}
.mini-player {
position: absolute;
bottom: 56px;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #282828;
border-top-width: 1px;
border-top-color: #333333;
border-top: 1px solid #333333;
cursor: pointer;
}
.mini-player:hover {
background-color: #333333;
}
.mini-player-art {
@@ -68,6 +78,7 @@
.mini-control-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
.section-header {
@@ -104,10 +115,12 @@
.recent-item {
min-width: 120px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
}
.recent-item:hover {
opacity: 0.9;
background-color: rgba(255, 255, 255, 0.05);
}
.recent-art {
@@ -214,11 +227,14 @@
}
.music-bottom-nav {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
height: 56px;
background-color: #1E1E1E;
border-top-width: 1px;
border-top-color: #282828;
border-top: 1px solid #282828;
}
.nav-item {
@@ -243,12 +259,29 @@
width: 28px;
height: 28px;
margin-bottom: 4px;
pointer-events: none;
}
.nav-item span {
font-size: 14px;
}
/* Progress bar for mini player */
.progress-container {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background-color: #404040;
}
.progress-bar {
width: 0%;
height: 100%;
background-color: #1DB954;
}
.bg-gradient-1 { background-color: #667eea; }
.bg-gradient-2 { background-color: #f093fb; }
.bg-gradient-3 { background-color: #4facfe; }
@@ -260,7 +293,7 @@
.bg-solid-blue { background-color: #2563eb; }
</style>
</head>
<body class="app-screen" onload="initLayout(document)">
<body class="app-screen" onload="initLayout(document); initMusic(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
@@ -278,7 +311,7 @@
</div>
<span class="app-bar-title">Music</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<div class="app-bar-action" onclick="openSearch()">
<img src="../../icons/search.tga"/>
</div>
</div>
@@ -292,28 +325,28 @@
</div>
<!-- Quick Access Grid -->
<div class="quick-access">
<div class="quick-card">
<div class="quick-access" id="quick-access">
<div class="quick-card" onclick="openPlaylist('liked')">
<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" onclick="openPlaylist('daily1')">
<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" onclick="openPlaylist('release')">
<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" onclick="openPlaylist('chill')">
<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" onclick="openPlaylist('workout')">
<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" onclick="openPlaylist('focus')">
<div class="quick-card-art bg-gradient-3">F</div>
<span class="quick-card-title">Focus Flow</span>
</div>
@@ -325,23 +358,23 @@
<span class="section-action">SEE ALL</span>
</div>
<div class="recent-row">
<div class="recent-item">
<div class="recent-row" id="recent-row">
<div class="recent-item" onclick="openPlaylist('pop')">
<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-item" onclick="openPlaylist('electronic')">
<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-item" onclick="openPlaylist('jazz')">
<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-item" onclick="openPlaylist('rock')">
<div class="recent-art bg-gradient-2">R</div>
<div class="recent-title">Rock Legends</div>
<div class="recent-subtitle">Playlist</div>
@@ -354,7 +387,7 @@
<span class="section-action">SEE ALL</span>
</div>
<div class="playlist-item">
<div class="playlist-item" onclick="openPlaylist('daily1')">
<div class="playlist-art bg-gradient-3">1</div>
<div class="playlist-info">
<div class="playlist-title">Daily Mix 1</div>
@@ -362,7 +395,7 @@
</div>
</div>
<div class="playlist-item">
<div class="playlist-item" onclick="openPlaylist('daily2')">
<div class="playlist-art bg-gradient-4">2</div>
<div class="playlist-info">
<div class="playlist-title">Daily Mix 2</div>
@@ -370,7 +403,7 @@
</div>
</div>
<div class="playlist-item">
<div class="playlist-item" onclick="openPlaylist('discover')">
<div class="playlist-art bg-gradient-5">D</div>
<div class="playlist-info">
<div class="playlist-title">Discover Weekly</div>
@@ -380,17 +413,20 @@
</div>
<!-- Mini Player -->
<div class="mini-player">
<div class="mini-player-art">M</div>
<div class="mini-player" onclick="openNowPlaying()">
<div class="progress-container">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div class="mini-player-art" id="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 class="mini-player-title" id="mini-player-title">Midnight City</div>
<div class="mini-player-artist" id="mini-player-artist">M83</div>
</div>
<div style="display: flex; gap: 4px;">
<div class="mini-control-btn">
<div class="mini-control-btn" onclick="toggleLike(); event.stopPropagation();">
<img src="../../icons/heart.tga"/>
</div>
<div class="mini-control-btn">
<div class="mini-control-btn" id="mini-play-btn" onclick="togglePlay(); event.stopPropagation();">
<img src="../../icons/play.tga"/>
</div>
</div>
@@ -402,11 +438,11 @@
<img src="../../icons/home.tga"/>
<span>Home</span>
</div>
<div class="nav-item">
<div class="nav-item" onclick="openSearch()">
<img src="../../icons/search.tga"/>
<span>Search</span>
</div>
<div class="nav-item">
<div class="nav-item" onclick="openLibrary()">
<img src="../../icons/library.tga"/>
<span>Library</span>
</div>

View File

@@ -0,0 +1,288 @@
-- settings.lua - Settings app functionality
-- Handles toggles, navigation, and system settings
local settings_doc = nil
-- Settings state
local settings_state = {
wifi = true,
wifi_network = "MosisNetwork",
bluetooth = false,
airplane_mode = false,
location = true,
location_mode = "High accuracy",
brightness = 80,
auto_brightness = true,
dark_mode = true,
font_size = "Default",
sleep_timeout = "5 minutes",
sound_volume = 70,
ring_volume = 80,
vibration = true,
dnd = false,
battery_percent = 85,
battery_status = "Not charging",
storage_used = 32,
storage_total = 128
}
-- Initialize settings
function initSettings(doc)
print("[Settings] Initializing...")
settings_doc = doc
updateAllToggles()
updateAllSubtitles()
end
-- Update all toggle states
function updateAllToggles()
updateToggle("wifi", settings_state.wifi)
updateToggle("bluetooth", settings_state.bluetooth)
updateToggle("airplane", settings_state.airplane_mode)
updateToggle("location", settings_state.location)
end
-- Update a single toggle
function updateToggle(name, state)
if not settings_doc then return end
local toggle = settings_doc:GetElementById("toggle-" .. name)
if toggle then
if state then
toggle:SetClass("active", true)
else
toggle:SetClass("active", false)
end
end
end
-- Update all subtitles
function updateAllSubtitles()
if not settings_doc then return end
-- WiFi
local wifi_sub = settings_doc:GetElementById("subtitle-wifi")
if wifi_sub then
if settings_state.wifi then
wifi_sub.inner_rml = "Connected to " .. settings_state.wifi_network
else
wifi_sub.inner_rml = "Off"
end
end
-- Bluetooth
local bt_sub = settings_doc:GetElementById("subtitle-bluetooth")
if bt_sub then
bt_sub.inner_rml = settings_state.bluetooth and "On" or "Off"
end
-- Battery
local bat_sub = settings_doc:GetElementById("subtitle-battery")
if bat_sub then
bat_sub.inner_rml = settings_state.battery_percent .. "% - " .. settings_state.battery_status
end
-- Storage
local storage_sub = settings_doc:GetElementById("subtitle-storage")
if storage_sub then
storage_sub.inner_rml = settings_state.storage_used .. " GB of " .. settings_state.storage_total .. " GB used"
end
-- Location
local loc_sub = settings_doc:GetElementById("subtitle-location")
if loc_sub then
if settings_state.location then
loc_sub.inner_rml = "On - " .. settings_state.location_mode
else
loc_sub.inner_rml = "Off"
end
end
end
-- Toggle WiFi
function toggleWifi()
settings_state.wifi = not settings_state.wifi
print("[Settings] WiFi: " .. tostring(settings_state.wifi))
updateToggle("wifi", settings_state.wifi)
updateAllSubtitles()
if showToast then
showToast(settings_state.wifi and "WiFi enabled" or "WiFi disabled")
end
end
-- Toggle Bluetooth
function toggleBluetooth()
settings_state.bluetooth = not settings_state.bluetooth
print("[Settings] Bluetooth: " .. tostring(settings_state.bluetooth))
updateToggle("bluetooth", settings_state.bluetooth)
updateAllSubtitles()
if showToast then
showToast(settings_state.bluetooth and "Bluetooth enabled" or "Bluetooth disabled")
end
end
-- Toggle Airplane Mode
function toggleAirplaneMode()
settings_state.airplane_mode = not settings_state.airplane_mode
print("[Settings] Airplane mode: " .. tostring(settings_state.airplane_mode))
if settings_state.airplane_mode then
-- Disable wireless when airplane mode is on
settings_state.wifi = false
settings_state.bluetooth = false
updateToggle("wifi", false)
updateToggle("bluetooth", false)
end
updateToggle("airplane", settings_state.airplane_mode)
updateAllSubtitles()
if showToast then
showToast(settings_state.airplane_mode and "Airplane mode on" or "Airplane mode off")
end
end
-- Toggle Location
function toggleLocation()
settings_state.location = not settings_state.location
print("[Settings] Location: " .. tostring(settings_state.location))
updateToggle("location", settings_state.location)
updateAllSubtitles()
if showToast then
showToast(settings_state.location and "Location enabled" or "Location disabled")
end
end
-- Open WiFi settings
function openWifiSettings()
print("[Settings] Opening WiFi settings...")
if navigateTo then
navigateTo("wifi_settings")
else
if showToast then
showToast("WiFi settings")
end
end
end
-- Open Bluetooth settings
function openBluetoothSettings()
print("[Settings] Opening Bluetooth settings...")
if showToast then
showToast("Bluetooth settings")
end
end
-- Open Display settings
function openDisplaySettings()
print("[Settings] Opening Display settings...")
if navigateTo then
navigateTo("display_settings")
else
if showToast then
showToast("Display settings")
end
end
end
-- Open Sound settings
function openSoundSettings()
print("[Settings] Opening Sound settings...")
if showToast then
showToast("Sound settings")
end
end
-- Open Notifications settings
function openNotificationsSettings()
print("[Settings] Opening Notifications settings...")
if showToast then
showToast("Notification settings")
end
end
-- Open Battery settings
function openBatterySettings()
print("[Settings] Opening Battery settings...")
if showToast then
showToast("Battery: " .. settings_state.battery_percent .. "%")
end
end
-- Open Storage settings
function openStorageSettings()
print("[Settings] Opening Storage settings...")
if showToast then
local used_percent = math.floor(settings_state.storage_used / settings_state.storage_total * 100)
showToast("Storage: " .. used_percent .. "% used")
end
end
-- Open Lock Screen settings
function openLockScreenSettings()
print("[Settings] Opening Lock Screen settings...")
if showToast then
showToast("Lock screen settings")
end
end
-- Open Privacy settings
function openPrivacySettings()
print("[Settings] Opening Privacy settings...")
if showToast then
showToast("Privacy settings")
end
end
-- Open Location settings
function openLocationSettings()
print("[Settings] Opening Location settings...")
if showToast then
showToast("Location settings")
end
end
-- Open About Phone
function openAboutPhone()
print("[Settings] Opening About Phone...")
if navigateTo then
navigateTo("about_phone")
else
if showToast then
showToast("Mosis Virtual Phone v1.0")
end
end
end
-- Open User Profile
function openUserProfile()
print("[Settings] Opening User Profile...")
if showToast then
showToast("User profile")
end
end
-- Search settings
function searchSettings(query)
print("[Settings] Searching: " .. query)
-- TODO: Implement settings search
end
-- Get setting value
function getSetting(key)
return settings_state[key]
end
-- Set setting value
function setSetting(key, value)
settings_state[key] = value
print("[Settings] Set " .. key .. " = " .. tostring(value))
updateAllToggles()
updateAllSubtitles()
end

View File

@@ -6,6 +6,7 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="settings.lua"></script>
<title>Settings</title>
<style>
.settings-list {
@@ -46,6 +47,10 @@
background-color: #252525;
}
.settings-item:active {
background-color: #2A2A2A;
}
.settings-item + .settings-item {
border-top: 1px #333333;
}
@@ -63,6 +68,7 @@
width: 32px;
height: 32px;
opacity: 0.7;
pointer-events: none;
}
.settings-content {
@@ -95,6 +101,10 @@
position: relative;
}
.settings-toggle:hover {
background-color: #777777;
}
.settings-toggle.active {
background-color: rgba(187, 134, 252, 0.5);
}
@@ -160,7 +170,7 @@
}
</style>
</head>
<body class="app-screen" onload="initLayout(document)">
<body class="app-screen" onload="initLayout(document); initSettings(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
@@ -188,7 +198,7 @@
<div class="app-content with-nav">
<div class="settings-list">
<!-- User Card -->
<div class="user-card">
<div class="user-card" onclick="openUserProfile()">
<div class="user-avatar">U</div>
<div class="user-info">
<div class="user-name">User</div>
@@ -200,38 +210,38 @@
<!-- Network Section -->
<div class="settings-section">
<div class="settings-header">Network</div>
<div class="settings-item">
<div class="settings-item" onclick="openWifiSettings()">
<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 class="settings-subtitle" id="subtitle-wifi">Connected to MosisNetwork</div>
</div>
<div class="settings-toggle active">
<div id="toggle-wifi" class="settings-toggle active" onclick="toggleWifi(); event.stopPropagation();">
<div class="settings-toggle-thumb"></div>
</div>
</div>
<div class="settings-item">
<div class="settings-item" onclick="openBluetoothSettings()">
<div class="settings-icon">
<img src="../../icons/signal.tga"/>
<img src="../../icons/bluetooth.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Bluetooth</div>
<div class="settings-subtitle">Off</div>
<div class="settings-subtitle" id="subtitle-bluetooth">Off</div>
</div>
<div class="settings-toggle">
<div id="toggle-bluetooth" class="settings-toggle" onclick="toggleBluetooth(); event.stopPropagation();">
<div class="settings-toggle-thumb"></div>
</div>
</div>
<div class="settings-item">
<div class="settings-icon">
<img src="../../icons/signal.tga"/>
<img src="../../icons/airplane.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Airplane Mode</div>
</div>
<div class="settings-toggle">
<div id="toggle-airplane" class="settings-toggle" onclick="toggleAirplaneMode()">
<div class="settings-toggle-thumb"></div>
</div>
</div>
@@ -240,9 +250,9 @@
<!-- Device Section -->
<div class="settings-section">
<div class="settings-header">Device</div>
<div class="settings-item">
<div class="settings-item" onclick="openDisplaySettings()">
<div class="settings-icon">
<img src="../../icons/settings.tga"/>
<img src="../../icons/brightness.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Display</div>
@@ -250,9 +260,9 @@
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-item" onclick="openSoundSettings()">
<div class="settings-icon">
<img src="../../icons/music.tga"/>
<img src="../../icons/volume.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Sound</div>
@@ -260,9 +270,9 @@
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-item" onclick="openNotificationsSettings()">
<div class="settings-icon">
<img src="../../icons/message.tga"/>
<img src="../../icons/notifications.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Notifications</div>
@@ -270,23 +280,23 @@
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-item" onclick="openBatterySettings()">
<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 class="settings-subtitle" id="subtitle-battery">85% - Not charging</div>
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-item" onclick="openStorageSettings()">
<div class="settings-icon">
<img src="../../icons/files.tga"/>
<img src="../../icons/storage.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Storage</div>
<div class="settings-subtitle">32 GB of 128 GB used</div>
<div class="settings-subtitle" id="subtitle-storage">32 GB of 128 GB used</div>
</div>
<span class="settings-action">></span>
</div>
@@ -295,9 +305,9 @@
<!-- Privacy Section -->
<div class="settings-section">
<div class="settings-header">Privacy &amp; Security</div>
<div class="settings-item">
<div class="settings-item" onclick="openLockScreenSettings()">
<div class="settings-icon">
<img src="../../icons/account.tga"/>
<img src="../../icons/lock.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Lock Screen</div>
@@ -305,9 +315,9 @@
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-item" onclick="openPrivacySettings()">
<div class="settings-icon">
<img src="../../icons/account.tga"/>
<img src="../../icons/privacy.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Privacy</div>
@@ -315,15 +325,15 @@
</div>
<span class="settings-action">></span>
</div>
<div class="settings-item">
<div class="settings-item" onclick="openLocationSettings()">
<div class="settings-icon">
<img src="../../icons/maps.tga"/>
<img src="../../icons/location.tga"/>
</div>
<div class="settings-content">
<div class="settings-title">Location</div>
<div class="settings-subtitle">On - High accuracy</div>
<div class="settings-subtitle" id="subtitle-location">On - High accuracy</div>
</div>
<div class="settings-toggle active">
<div id="toggle-location" class="settings-toggle active" onclick="toggleLocation(); event.stopPropagation();">
<div class="settings-toggle-thumb"></div>
</div>
</div>
@@ -332,7 +342,7 @@
<!-- About Section -->
<div class="settings-section">
<div class="settings-header">About</div>
<div class="settings-item">
<div class="settings-item" onclick="openAboutPhone()">
<div class="settings-icon">
<img src="../../icons/phone.tga"/>
</div>

View File

@@ -1,11 +1,138 @@
<!-- Browser App Content Fragment -->
<!-- Styles are in shell.rml -->
<!-- Uses classes from components.rcss -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Browser</span>
<!-- URL bar -->
<div class="app-bar" style="padding: 8px 12px;">
<div class="btn-icon" onclick="browserBack()" style="width: 40px; height: 40px;">
<img src="../../icons/back.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="app-body">
<span class="placeholder-text">Browser App</span>
<div class="search-bar" style="flex: 1; margin: 0 8px; padding: 8px 16px;">
<span style="color: #4CAF50; margin-right: 8px;">S</span>
<input type="text" id="url-input" style="flex: 1; background-color: transparent; color: #FFFFFF; font-size: 14px;" value="mosis.app"/>
</div>
<div class="btn-icon" onclick="browserRefresh()" style="width: 40px; height: 40px;">
<img src="../../icons/refresh.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="btn-icon" onclick="showTabs()" style="width: 40px; height: 40px;">
<span style="font-size: 14px; color: #FFFFFF; font-weight: bold;">1</span>
</div>
</div>
<!-- Page content -->
<div style="flex: 1; overflow: auto; background-color: #FFFFFF; padding: 24px;">
<div style="text-align: center;">
<div style="font-size: 48px; font-weight: bold; color: #BB86FC; margin-bottom: 24px;">Mosis</div>
<div class="search-bar" style="margin-bottom: 32px; background-color: #f1f3f4;">
<input type="text" style="flex: 1; background-color: transparent; color: #333333; font-size: 16px; text-align: center;" placeholder="Search or enter URL"/>
</div>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 16px;">
<div onclick="navigateTo('google.com')" style="cursor: pointer; width: 80px; display: flex; flex-direction: column; align-items: center;">
<div style="width: 48px; height: 48px; border-radius: 8px; background-color: #4285F4; display: flex; align-items: center; justify-content: center; color: #FFFFFF; font-size: 20px; margin-bottom: 8px;">G</div>
<span style="font-size: 12px; color: #666666;">Google</span>
</div>
<div onclick="navigateTo('youtube.com')" style="cursor: pointer; width: 80px; display: flex; flex-direction: column; align-items: center;">
<div style="width: 48px; height: 48px; border-radius: 8px; background-color: #FF0000; display: flex; align-items: center; justify-content: center; color: #FFFFFF; font-size: 20px; margin-bottom: 8px;">Y</div>
<span style="font-size: 12px; color: #666666;">YouTube</span>
</div>
<div onclick="navigateTo('github.com')" style="cursor: pointer; width: 80px; display: flex; flex-direction: column; align-items: center;">
<div style="width: 48px; height: 48px; border-radius: 8px; background-color: #333333; display: flex; align-items: center; justify-content: center; color: #FFFFFF; font-size: 20px; margin-bottom: 8px;">G</div>
<span style="font-size: 12px; color: #666666;">GitHub</span>
</div>
<div onclick="navigateTo('reddit.com')" style="cursor: pointer; width: 80px; display: flex; flex-direction: column; align-items: center;">
<div style="width: 48px; height: 48px; border-radius: 8px; background-color: #FF4500; display: flex; align-items: center; justify-content: center; color: #FFFFFF; font-size: 20px; margin-bottom: 8px;">R</div>
<span style="font-size: 12px; color: #666666;">Reddit</span>
</div>
</div>
</div>
</div>
<!-- Bottom bar -->
<div class="bottom-nav">
<div class="bottom-nav-item" onclick="browserBack()">
<img src="../../icons/back.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="bottom-nav-item" onclick="browserForward()">
<img src="../../icons/forward.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="bottom-nav-item" onclick="browserHome()">
<img src="../../icons/home.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="bottom-nav-item" onclick="addBookmark()">
<img src="../../icons/star.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="bottom-nav-item" onclick="showBrowserMenu()">
<img src="../../icons/menu.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
</div>
<script>
local current_url = "mosis.app"
function navigateTo(url)
print("[Browser] Navigating to: " .. url)
current_url = url
local doc = shell_document
if doc then
local url_input = doc:GetElementById("url-input")
if url_input then
url_input.value = url
end
end
if showToast then
showToast("Loading " .. url)
end
end
function browserBack()
print("[Browser] Back")
if showToast then
showToast("Back")
end
end
function browserForward()
print("[Browser] Forward")
if showToast then
showToast("No forward history")
end
end
function browserRefresh()
print("[Browser] Refresh")
if showToast then
showToast("Page refreshed")
end
end
function browserHome()
print("[Browser] Home")
navigateTo("mosis.app")
end
function showTabs()
print("[Browser] Show tabs")
if showToast then
showToast("1 tab open")
end
end
function addBookmark()
print("[Browser] Add bookmark")
if showToast then
showToast("Bookmark added")
end
end
function showBrowserMenu()
print("[Browser] Menu")
if showToast then
showToast("Browser menu")
end
end
print("[Browser] Content loaded")
</script>

View File

@@ -1,44 +1,281 @@
<!-- Camera App Content Fragment -->
<!-- Loaded into shell's #app-container -->
<!-- Styles are in shell.rml -->
<!-- Uses classes from shell.rml -->
<div class="camera-content">
<!-- Top Bar -->
<div class="camera-top-bar">
<div class="camera-btn" onclick="showToast('Flash: Auto')">
<img src="../../icons/flash.tga"/>
<div style="display: flex; gap: 12px;">
<div class="camera-btn" id="flash-btn" onclick="toggleFlash()">
<img src="../../icons/flash.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="camera-btn">
<img src="../../icons/timer.tga"/>
<div class="camera-btn" onclick="toggleTimer()">
<img src="../../icons/timer.tga" style="width: 24px; height: 24px;"/>
</div>
<div class="camera-btn" onclick="showToast('Settings')">
<img src="../../icons/settings.tga"/>
</div>
<span id="flash-indicator" style="font-size: 12px; color: #FFFFFF; background-color: rgba(0, 0, 0, 0.4); padding: 6px 12px; border-radius: 16px;">Flash: Auto</span>
<div class="camera-btn" onclick="openCameraSettings()">
<img src="../../icons/settings.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<!-- Recording Indicator -->
<div id="recording-indicator" style="position: absolute; top: 80px; left: 50%; display: none; align-items: center; gap: 8px; background-color: rgba(0, 0, 0, 0.6); padding: 8px 16px; border-radius: 20px;">
<div style="width: 12px; height: 12px; border-radius: 6px; background-color: #F44336;"></div>
<span id="recording-time" style="font-size: 14px; color: #FFFFFF; font-weight: bold;">00:00</span>
</div>
<!-- Viewfinder -->
<div class="viewfinder-container">
<div class="viewfinder">
<span class="viewfinder-text">Camera Preview</span>
<div class="viewfinder-container" onclick="tapToFocus()">
<div class="viewfinder" id="viewfinder">
<span style="font-size: 64px; color: #444444; margin-bottom: 16px;">C</span>
<span style="color: #666666; font-size: 18px;">Camera Preview</span>
<span style="font-size: 14px; color: #555555; margin-top: 8px;">Tap to focus</span>
</div>
<div class="focus-indicator"></div>
<div id="focus-indicator" style="position: absolute; width: 80px; height: 80px; border-radius: 8px; opacity: 0;"></div>
</div>
<!-- Zoom Controls -->
<div style="position: absolute; bottom: 180px; left: 50%; display: flex; gap: 16px;">
<div onclick="setZoom(0.5)" style="width: 40px; height: 40px; border-radius: 20px; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; color: #FFFFFF; font-size: 12px; font-weight: bold; cursor: pointer;">0.5x</div>
<div id="zoom-1x" onclick="setZoom(1)" style="width: 40px; height: 40px; border-radius: 20px; background-color: #FFFFFF; display: flex; align-items: center; justify-content: center; color: #000000; font-size: 12px; font-weight: bold; cursor: pointer;">1x</div>
<div onclick="setZoom(2)" style="width: 40px; height: 40px; border-radius: 20px; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; color: #FFFFFF; font-size: 12px; font-weight: bold; cursor: pointer;">2x</div>
</div>
<!-- Mode Selector -->
<div class="mode-selector">
<span class="mode-item">Video</span>
<span class="mode-item active">Photo</span>
<span class="mode-item">Portrait</span>
<div class="camera-mode-selector">
<span class="camera-mode" id="mode-night" onclick="switchMode('night')">Night</span>
<span class="camera-mode" id="mode-portrait" onclick="switchMode('portrait')">Portrait</span>
<span class="camera-mode active" id="mode-photo" onclick="switchMode('photo')">Photo</span>
<span class="camera-mode" id="mode-video" onclick="switchMode('video')">Video</span>
</div>
<!-- Bottom Controls -->
<div class="camera-bottom-bar">
<div class="gallery-preview"></div>
<div class="shutter-btn" onclick="showToast('Photo captured!', 'success')">
<div class="shutter-btn-inner"></div>
<div class="camera-gallery-preview" onclick="openGallery()"></div>
<div class="camera-shutter-btn" id="shutter-btn" onclick="capture()">
<div id="shutter-inner" class="camera-shutter-inner"></div>
</div>
<div class="switch-camera-btn" onclick="showToast('Switched camera')">
<img src="../../icons/switch-camera.tga"/>
<div class="camera-switch-btn" onclick="switchCamera()">
<img src="../../icons/switch-camera.tga" style="width: 28px; height: 28px;"/>
</div>
</div>
</div>
<script>
local current_mode = "photo"
local flash_mode = "auto"
local timer_mode = "off"
local is_front = false
local is_recording = false
local record_time = 0
local record_timer = nil
local zoom_level = 1
local function getDoc()
return shell_document
end
function switchMode(mode)
current_mode = mode
local doc = getDoc()
if not doc then return end
local modes = {"night", "portrait", "photo", "video"}
for _, m in ipairs(modes) do
local el = doc:GetElementById("mode-" .. m)
if el then
el:SetClass("active", m == mode)
end
end
local inner = doc:GetElementById("shutter-inner")
if inner then
if mode == "video" then
inner:SetClass("camera-shutter-inner", false)
inner:SetClass("camera-shutter-video", true)
else
inner:SetClass("camera-shutter-video", false)
inner:SetClass("camera-shutter-inner", true)
end
end
print("[Camera] Mode: " .. mode)
if showToast then
showToast(mode:sub(1,1):upper() .. mode:sub(2) .. " mode")
end
end
function toggleFlash()
if flash_mode == "auto" then
flash_mode = "on"
elseif flash_mode == "on" then
flash_mode = "off"
else
flash_mode = "auto"
end
local doc = getDoc()
if doc then
local indicator = doc:GetElementById("flash-indicator")
if indicator then
indicator.inner_rml = "Flash: " .. flash_mode:sub(1,1):upper() .. flash_mode:sub(2)
end
end
print("[Camera] Flash: " .. flash_mode)
end
function toggleTimer()
if timer_mode == "off" then
timer_mode = "3s"
elseif timer_mode == "3s" then
timer_mode = "10s"
else
timer_mode = "off"
end
print("[Camera] Timer: " .. timer_mode)
if showToast then
showToast("Timer: " .. timer_mode)
end
end
function setZoom(level)
zoom_level = level
print("[Camera] Zoom: " .. level .. "x")
if showToast then
showToast(level .. "x zoom")
end
end
function switchCamera()
is_front = not is_front
local doc = getDoc()
if doc then
local vf = doc:GetElementById("viewfinder")
if vf then
if is_front then
vf.inner_rml = [[
<span style="font-size: 64px; color: #444444; margin-bottom: 16px;">F</span>
<span style="color: #666666; font-size: 18px;">Front Camera</span>
<span style="font-size: 14px; color: #555555; margin-top: 8px;">Tap to focus</span>
]]
else
vf.inner_rml = [[
<span style="font-size: 64px; color: #444444; margin-bottom: 16px;">C</span>
<span style="color: #666666; font-size: 18px;">Camera Preview</span>
<span style="font-size: 14px; color: #555555; margin-top: 8px;">Tap to focus</span>
]]
end
end
end
print("[Camera] Switched to " .. (is_front and "front" or "back") .. " camera")
if showToast then
showToast(is_front and "Front camera" or "Back camera")
end
end
function capture()
if current_mode == "video" then
if is_recording then
stopRecording()
else
startRecording()
end
else
takePhoto()
end
end
function takePhoto()
print("[Camera] Photo captured!")
if showToast then
showToast("Photo saved")
end
end
function startRecording()
is_recording = true
record_time = 0
local doc = getDoc()
if doc then
local indicator = doc:GetElementById("recording-indicator")
if indicator then
indicator.style.display = "flex"
end
local inner = doc:GetElementById("shutter-inner")
if inner then
inner:SetClass("camera-shutter-video", false)
inner:SetClass("camera-shutter-stop", true)
end
end
if setInterval then
record_timer = setInterval(function()
record_time = record_time + 1
local mins = math.floor(record_time / 60)
local secs = record_time % 60
local doc = getDoc()
if doc then
local time_el = doc:GetElementById("recording-time")
if time_el then
time_el.inner_rml = string.format("%02d:%02d", mins, secs)
end
end
end, 1000)
end
print("[Camera] Recording started")
end
function stopRecording()
is_recording = false
if record_timer and clearInterval then
clearInterval(record_timer)
record_timer = nil
end
local doc = getDoc()
if doc then
local indicator = doc:GetElementById("recording-indicator")
if indicator then
indicator.style.display = "none"
end
local inner = doc:GetElementById("shutter-inner")
if inner then
inner:SetClass("camera-shutter-stop", false)
inner:SetClass("camera-shutter-video", true)
end
end
local mins = math.floor(record_time / 60)
local secs = record_time % 60
print("[Camera] Recording stopped: " .. string.format("%02d:%02d", mins, secs))
if showToast then
showToast(string.format("Video saved (%02d:%02d)", mins, secs))
end
end
function tapToFocus()
print("[Camera] Tap to focus")
end
function openGallery()
print("[Camera] Opening gallery")
if showToast then
showToast("Gallery")
end
end
function openCameraSettings()
print("[Camera] Camera settings")
if showToast then
showToast("Camera settings")
end
end
print("[Camera] Content loaded")
</script>

View File

@@ -1,11 +1,143 @@
<!-- Contacts App Content Fragment -->
<!-- Styles are in shell.rml -->
<!-- Uses classes from components.rcss -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Contacts</span>
<div class="app-bar">
<span class="app-bar-title">Contacts</span>
</div>
<div class="app-body">
<span class="placeholder-text">Contacts App</span>
<div class="search-bar">
<img src="../../icons/search.tga" style="width: 24px; height: 24px; margin-right: 12px; opacity: 0.6;"/>
<span class="search-input">Search contacts</span>
</div>
<div class="list" style="flex: 1; overflow: auto;">
<div class="list-header">A</div>
<div class="list-item" onclick="openContact('Alice Johnson')">
<div class="list-item-avatar" style="background-color: #4CAF50;">A</div>
<div class="list-item-content">
<span class="list-item-title">Alice Johnson</span>
<span class="list-item-subtitle">+1 555-0101</span>
</div>
<div class="btn-icon" onclick="callContact('Alice Johnson'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<div class="list-header">B</div>
<div class="list-item" onclick="openContact('Bob Williams')">
<div class="list-item-avatar" style="background-color: #2196F3;">B</div>
<div class="list-item-content">
<span class="list-item-title">Bob Williams</span>
<span class="list-item-subtitle">+1 555-0102</span>
</div>
<div class="btn-icon" onclick="callContact('Bob Williams'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<div class="list-header">C</div>
<div class="list-item" onclick="openContact('Carol Davis')">
<div class="list-item-avatar" style="background-color: #9C27B0;">C</div>
<div class="list-item-content">
<span class="list-item-title">Carol Davis</span>
<span class="list-item-subtitle">+1 555-0103</span>
</div>
<div class="btn-icon" onclick="callContact('Carol Davis'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<div class="list-header">D</div>
<div class="list-item" onclick="openContact('David Miller')">
<div class="list-item-avatar" style="background-color: #FF9800;">D</div>
<div class="list-item-content">
<span class="list-item-title">David Miller</span>
<span class="list-item-subtitle">+1 555-0104</span>
</div>
<div class="btn-icon" onclick="callContact('David Miller'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<div class="list-header">E</div>
<div class="list-item" onclick="openContact('Emma Wilson')">
<div class="list-item-avatar" style="background-color: #E91E63;">E</div>
<div class="list-item-content">
<span class="list-item-title">Emma Wilson</span>
<span class="list-item-subtitle">+1 555-0105</span>
</div>
<div class="btn-icon" onclick="callContact('Emma Wilson'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<div class="list-header">J</div>
<div class="list-item" onclick="openContact('John Smith')">
<div class="list-item-avatar" style="background-color: #3F51B5;">J</div>
<div class="list-item-content">
<span class="list-item-title">John Smith</span>
<span class="list-item-subtitle">+1 555-0109</span>
</div>
<div class="btn-icon" onclick="callContact('John Smith'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<div class="list-header">M</div>
<div class="list-item" onclick="openContact('Mom')">
<div class="list-item-avatar" style="background-color: #E91E63;">M</div>
<div class="list-item-content">
<span class="list-item-title">Mom</span>
<span class="list-item-subtitle">+1 555-0110</span>
</div>
<div class="btn-icon" onclick="callContact('Mom'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
<div class="list-header">S</div>
<div class="list-item" onclick="openContact('Sarah')">
<div class="list-item-avatar" style="background-color: #9C27B0;">S</div>
<div class="list-item-content">
<span class="list-item-title">Sarah</span>
<span class="list-item-subtitle">+1 555-0111</span>
</div>
<div class="btn-icon" onclick="callContact('Sarah'); event.stopPropagation();">
<img src="../../icons/phone.tga" style="width: 24px; height: 24px;"/>
</div>
</div>
</div>
<div class="btn-fab" onclick="addContact()">
<img src="../../icons/add.tga" style="width: 32px; height: 32px;"/>
</div>
</div>
<script>
function openContact(name)
print("[Contacts] Opening contact: " .. name)
if showToast then
showToast("Contact: " .. name)
end
end
function callContact(name)
print("[Contacts] Calling: " .. name)
if showToast then
showToast("Calling " .. name .. "...")
end
if shellNavigateTo then
shellNavigateTo("calling")
end
end
function addContact()
print("[Contacts] Add new contact")
if showToast then
showToast("Add new contact")
end
end
print("[Contacts] Content loaded")
</script>

View File

@@ -0,0 +1,468 @@
<!-- Calling Screen Content Fragment -->
<!-- In-call interface with controls -->
<style>
.calling-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
}
.calling-info {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
}
.calling-avatar {
width: 120px;
height: 120px;
border-radius: 60px;
background-color: #BB86FC;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: #FFFFFF;
font-weight: 500;
margin-bottom: 24px;
}
.calling-name {
font-size: 32px;
color: #FFFFFF;
font-weight: 500;
margin-bottom: 8px;
}
.calling-status {
font-size: 18px;
color: #B0B0B0;
margin-bottom: 8px;
}
.calling-timer {
font-size: 24px;
color: #FFFFFF;
font-weight: 300;
}
/* Call controls */
.call-controls {
padding: 32px 24px 48px;
}
.controls-row {
display: flex;
justify-content: center;
gap: 32px;
margin-bottom: 32px;
}
.control-btn {
width: 64px;
height: 64px;
border-radius: 32px;
background-color: rgba(255, 255, 255, 0.15);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.control-btn:hover {
background-color: rgba(255, 255, 255, 0.25);
}
.control-btn.active {
background-color: #FFFFFF;
}
.control-btn.active img {
filter: invert(1);
}
.control-btn img {
width: 28px;
height: 28px;
}
.control-label {
font-size: 12px;
color: #FFFFFF;
margin-top: 8px;
text-align: center;
}
.control-btn.active .control-label {
color: #000000;
}
/* End call button */
.end-call-row {
display: flex;
justify-content: center;
}
.end-call-btn {
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: 36px;
height: 36px;
transform: rotate(135deg);
}
/* Dialpad overlay */
.dialpad-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: #1a1a1a;
padding: 24px;
border-radius: 24px 24px 0 0;
display: none;
}
.dialpad-overlay.visible {
display: block;
}
.dialpad-row {
display: flex;
justify-content: center;
gap: 24px;
margin-bottom: 16px;
}
.dialpad-key {
width: 64px;
height: 64px;
border-radius: 32px;
background-color: #2d2d2d;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dialpad-key:hover {
background-color: #3d3d3d;
}
.dialpad-digit {
font-size: 24px;
color: #FFFFFF;
}
.dialpad-letters {
font-size: 10px;
color: #888888;
}
.dialpad-close {
text-align: center;
padding: 16px;
color: #BB86FC;
font-size: 16px;
cursor: pointer;
}
</style>
<div class="calling-content">
<div class="calling-info">
<div id="calling-avatar" class="calling-avatar">J</div>
<div id="calling-name" class="calling-name">John Smith</div>
<div id="calling-status" class="calling-status">Calling...</div>
<div id="calling-timer" class="calling-timer"></div>
</div>
<div class="call-controls">
<div class="controls-row">
<div class="control-btn" id="btn-mute" onclick="toggleMute()">
<img src="../../icons/mic.tga"/>
<span class="control-label">Mute</span>
</div>
<div class="control-btn" id="btn-keypad" onclick="toggleKeypad()">
<img src="../../icons/keypad.tga"/>
<span class="control-label">Keypad</span>
</div>
<div class="control-btn" id="btn-speaker" onclick="toggleSpeaker()">
<img src="../../icons/speaker.tga"/>
<span class="control-label">Speaker</span>
</div>
</div>
<div class="controls-row">
<div class="control-btn" id="btn-add" onclick="addCall()">
<img src="../../icons/add.tga"/>
<span class="control-label">Add call</span>
</div>
<div class="control-btn" id="btn-hold" onclick="toggleHold()">
<img src="../../icons/pause.tga"/>
<span class="control-label">Hold</span>
</div>
<div class="control-btn" id="btn-video" onclick="toggleVideo()">
<img src="../../icons/camera.tga"/>
<span class="control-label">Video</span>
</div>
</div>
<div class="end-call-row">
<div class="end-call-btn" onclick="endCall()">
<img src="../../icons/phone.tga"/>
</div>
</div>
</div>
<!-- Dialpad overlay -->
<div id="dialpad-overlay" class="dialpad-overlay">
<div class="dialpad-row">
<div class="dialpad-key" onclick="dialDTMF('1')">
<span class="dialpad-digit">1</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('2')">
<span class="dialpad-digit">2</span>
<span class="dialpad-letters">ABC</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('3')">
<span class="dialpad-digit">3</span>
<span class="dialpad-letters">DEF</span>
</div>
</div>
<div class="dialpad-row">
<div class="dialpad-key" onclick="dialDTMF('4')">
<span class="dialpad-digit">4</span>
<span class="dialpad-letters">GHI</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('5')">
<span class="dialpad-digit">5</span>
<span class="dialpad-letters">JKL</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('6')">
<span class="dialpad-digit">6</span>
<span class="dialpad-letters">MNO</span>
</div>
</div>
<div class="dialpad-row">
<div class="dialpad-key" onclick="dialDTMF('7')">
<span class="dialpad-digit">7</span>
<span class="dialpad-letters">PQRS</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('8')">
<span class="dialpad-digit">8</span>
<span class="dialpad-letters">TUV</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('9')">
<span class="dialpad-digit">9</span>
<span class="dialpad-letters">WXYZ</span>
</div>
</div>
<div class="dialpad-row">
<div class="dialpad-key" onclick="dialDTMF('*')">
<span class="dialpad-digit">*</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('0')">
<span class="dialpad-digit">0</span>
<span class="dialpad-letters">+</span>
</div>
<div class="dialpad-key" onclick="dialDTMF('#')">
<span class="dialpad-digit">#</span>
</div>
</div>
<div class="dialpad-close" onclick="toggleKeypad()">Hide</div>
</div>
</div>
<script>
-- Calling state
local call_state = {
is_muted = false,
is_speaker = false,
is_hold = false,
is_connected = false,
call_duration = 0,
keypad_visible = false
}
local timer_id = nil
local function getDoc()
return shell_document
end
-- Simulate call connection
local function simulateConnection()
if setTimeout then
setTimeout(function()
call_state.is_connected = true
local doc = getDoc()
if doc then
local status = doc:GetElementById("calling-status")
if status then
status.inner_rml = "Connected"
end
end
startCallTimer()
end, 2000)
end
end
-- Start call timer
function startCallTimer()
if setInterval then
timer_id = setInterval(function()
call_state.call_duration = call_state.call_duration + 1
updateTimerDisplay()
end, 1000)
end
end
-- Update timer display
function updateTimerDisplay()
local doc = getDoc()
if not doc then return end
local timer = doc:GetElementById("calling-timer")
if timer then
local mins = math.floor(call_state.call_duration / 60)
local secs = call_state.call_duration % 60
timer.inner_rml = string.format("%02d:%02d", mins, secs)
end
end
-- Toggle mute
function toggleMute()
call_state.is_muted = not call_state.is_muted
local doc = getDoc()
if doc then
local btn = doc:GetElementById("btn-mute")
if btn then
btn:SetClass("active", call_state.is_muted)
end
end
if showToast then
showToast(call_state.is_muted and "Muted" or "Unmuted")
end
print("[Calling] Mute: " .. tostring(call_state.is_muted))
end
-- Toggle speaker
function toggleSpeaker()
call_state.is_speaker = not call_state.is_speaker
local doc = getDoc()
if doc then
local btn = doc:GetElementById("btn-speaker")
if btn then
btn:SetClass("active", call_state.is_speaker)
end
end
if showToast then
showToast(call_state.is_speaker and "Speaker on" or "Speaker off")
end
print("[Calling] Speaker: " .. tostring(call_state.is_speaker))
end
-- Toggle hold
function toggleHold()
call_state.is_hold = not call_state.is_hold
local doc = getDoc()
if doc then
local btn = doc:GetElementById("btn-hold")
if btn then
btn:SetClass("active", call_state.is_hold)
end
local status = doc:GetElementById("calling-status")
if status then
if call_state.is_hold then
status.inner_rml = "On Hold"
elseif call_state.is_connected then
status.inner_rml = "Connected"
end
end
end
if showToast then
showToast(call_state.is_hold and "Call on hold" or "Call resumed")
end
print("[Calling] Hold: " .. tostring(call_state.is_hold))
end
-- Toggle keypad
function toggleKeypad()
call_state.keypad_visible = not call_state.keypad_visible
local doc = getDoc()
if doc then
local overlay = doc:GetElementById("dialpad-overlay")
if overlay then
overlay:SetClass("visible", call_state.keypad_visible)
end
end
print("[Calling] Keypad: " .. tostring(call_state.keypad_visible))
end
-- Dial DTMF tone
function dialDTMF(digit)
print("[Calling] DTMF: " .. digit)
if showToast then
showToast("DTMF: " .. digit)
end
end
-- Add call
function addCall()
if showToast then
showToast("Add call not available")
end
end
-- Toggle video
function toggleVideo()
if showToast then
showToast("Video call not available")
end
end
-- End call
function endCall()
print("[Calling] Ending call")
-- Stop timer
if timer_id and clearInterval then
clearInterval(timer_id)
timer_id = nil
end
if showToast then
local mins = math.floor(call_state.call_duration / 60)
local secs = call_state.call_duration % 60
showToast(string.format("Call ended (%02d:%02d)", mins, secs))
end
-- Navigate back
if shellGoBack then
shellGoBack()
end
end
-- Initialize on load
simulateConnection()
print("[Calling] Content loaded")
</script>

View File

@@ -1,11 +1,261 @@
<!-- Dialer App Content Fragment -->
<!-- Styles are in shell.rml -->
<!-- Uses classes from components.rcss -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Phone</span>
<!-- Tabs -->
<div class="bottom-nav" style="height: 56px; position: relative; top: 0;">
<div id="tab-keypad" class="bottom-nav-item active" onclick="switchDialerTab('keypad')">
<span style="font-size: 14px; font-weight: 600;">Keypad</span>
</div>
<div class="app-body">
<span class="placeholder-text">Dialer App</span>
<div id="tab-recent" class="bottom-nav-item" onclick="switchDialerTab('recent')">
<span style="font-size: 14px; font-weight: 600;">Recent</span>
</div>
<div id="tab-contacts" class="bottom-nav-item" onclick="switchDialerTab('contacts')">
<span style="font-size: 14px; font-weight: 600;">Contacts</span>
</div>
</div>
<!-- Keypad View -->
<div id="view-keypad" style="flex: 1; display: flex; flex-direction: column;">
<div class="dial-display" id="dialer-number"></div>
<div class="dial-pad">
<div class="dial-key" onclick="dialDigit('1')">
<span class="dial-key-number">1</span>
<span class="dial-key-letters"></span>
</div>
<div class="dial-key" onclick="dialDigit('2')">
<span class="dial-key-number">2</span>
<span class="dial-key-letters">ABC</span>
</div>
<div class="dial-key" onclick="dialDigit('3')">
<span class="dial-key-number">3</span>
<span class="dial-key-letters">DEF</span>
</div>
<div class="dial-key" onclick="dialDigit('4')">
<span class="dial-key-number">4</span>
<span class="dial-key-letters">GHI</span>
</div>
<div class="dial-key" onclick="dialDigit('5')">
<span class="dial-key-number">5</span>
<span class="dial-key-letters">JKL</span>
</div>
<div class="dial-key" onclick="dialDigit('6')">
<span class="dial-key-number">6</span>
<span class="dial-key-letters">MNO</span>
</div>
<div class="dial-key" onclick="dialDigit('7')">
<span class="dial-key-number">7</span>
<span class="dial-key-letters">PQRS</span>
</div>
<div class="dial-key" onclick="dialDigit('8')">
<span class="dial-key-number">8</span>
<span class="dial-key-letters">TUV</span>
</div>
<div class="dial-key" onclick="dialDigit('9')">
<span class="dial-key-number">9</span>
<span class="dial-key-letters">WXYZ</span>
</div>
<div class="dial-key" onclick="dialDigit('*')">
<span class="dial-key-number">*</span>
<span class="dial-key-letters"></span>
</div>
<div class="dial-key" onclick="dialDigit('0')">
<span class="dial-key-number">0</span>
<span class="dial-key-letters">+</span>
</div>
<div class="dial-key" onclick="dialDigit('#')">
<span class="dial-key-number">#</span>
<span class="dial-key-letters"></span>
</div>
</div>
<div class="dial-actions">
<div class="btn-icon" onclick="openVideoCall()">
<img src="../../icons/camera.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="dial-call-btn" onclick="makeCall()">
<img src="../../icons/phone.tga" style="width: 36px; height: 36px;"/>
</div>
<div class="btn-icon" onclick="deleteDigit()">
<img src="../../icons/back.tga" style="width: 28px; height: 28px;"/>
</div>
</div>
</div>
<!-- Recent Calls View -->
<div id="view-recent" style="flex: 1; overflow: auto; display: none;">
<div class="list">
<div class="list-item" onclick="callContact('Mom')">
<div class="list-item-avatar" style="background-color: #E91E63;">M</div>
<div class="list-item-content">
<span class="list-item-title">Mom</span>
<span class="list-item-subtitle" style="color: #4CAF50;">Outgoing - 5 min</span>
</div>
<span style="font-size: 14px; color: #888888;">10:30 AM</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="callContact('John Smith')">
<div class="list-item-avatar" style="background-color: #2196F3;">J</div>
<div class="list-item-content">
<span class="list-item-title">John Smith</span>
<span class="list-item-subtitle" style="color: #F44336;">Missed</span>
</div>
<span style="font-size: 14px; color: #888888;">Yesterday</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="callContact('Work')">
<div class="list-item-avatar" style="background-color: #FF9800;">W</div>
<div class="list-item-content">
<span class="list-item-title">Work</span>
<span class="list-item-subtitle">Incoming - 12 min</span>
</div>
<span style="font-size: 14px; color: #888888;">Yesterday</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="callContact('Sarah')">
<div class="list-item-avatar" style="background-color: #9C27B0;">S</div>
<div class="list-item-content">
<span class="list-item-title">Sarah</span>
<span class="list-item-subtitle" style="color: #4CAF50;">Outgoing - 23 min</span>
</div>
<span style="font-size: 14px; color: #888888;">Monday</span>
</div>
</div>
</div>
<!-- Contacts View -->
<div id="view-contacts" style="flex: 1; overflow: auto; display: none;">
<div class="search-bar">
<img src="../../icons/search.tga" style="width: 24px; height: 24px; margin-right: 12px; opacity: 0.6;"/>
<span style="color: #B3B3B3;">Search contacts</span>
</div>
<div class="list">
<div class="list-item" onclick="callContact('Alice Johnson')">
<div class="list-item-avatar" style="background-color: #4CAF50;">A</div>
<span class="list-item-title">Alice Johnson</span>
</div>
<div class="list-item" onclick="callContact('Bob Williams')">
<div class="list-item-avatar" style="background-color: #2196F3;">B</div>
<span class="list-item-title">Bob Williams</span>
</div>
<div class="list-item" onclick="callContact('Carol Davis')">
<div class="list-item-avatar" style="background-color: #9C27B0;">C</div>
<span class="list-item-title">Carol Davis</span>
</div>
<div class="list-item" onclick="callContact('David Miller')">
<div class="list-item-avatar" style="background-color: #FF9800;">D</div>
<span class="list-item-title">David Miller</span>
</div>
<div class="list-item" onclick="callContact('Emma Wilson')">
<div class="list-item-avatar" style="background-color: #E91E63;">E</div>
<span class="list-item-title">Emma Wilson</span>
</div>
</div>
</div>
</div>
<script>
local dialed_number = ""
local current_tab = "keypad"
local function getDoc()
return shell_document
end
function switchDialerTab(tab)
current_tab = tab
local doc = getDoc()
if not doc then return end
local tabs = {"keypad", "recent", "contacts"}
for _, t in ipairs(tabs) do
local tab_el = doc:GetElementById("tab-" .. t)
local view_el = doc:GetElementById("view-" .. t)
if tab_el then
if t == tab then
tab_el:SetClass("active", true)
else
tab_el:SetClass("active", false)
end
end
if view_el then
if t == tab then
view_el.style.display = "flex"
else
view_el.style.display = "none"
end
end
end
print("[Dialer] Switched to tab: " .. tab)
end
function dialDigit(digit)
dialed_number = dialed_number .. digit
updateDisplay()
print("[Dialer] Dialed: " .. digit .. " -> " .. dialed_number)
end
function deleteDigit()
if #dialed_number > 0 then
dialed_number = dialed_number:sub(1, -2)
updateDisplay()
print("[Dialer] Deleted digit -> " .. dialed_number)
end
end
function updateDisplay()
local doc = getDoc()
if not doc then return end
local display = doc:GetElementById("dialer-number")
if display then
local formatted = dialed_number
if #dialed_number > 6 then
formatted = dialed_number:sub(1, 3) .. "-" .. dialed_number:sub(4, 6) .. "-" .. dialed_number:sub(7)
elseif #dialed_number > 3 then
formatted = dialed_number:sub(1, 3) .. "-" .. dialed_number:sub(4)
end
display.inner_rml = formatted
end
end
function makeCall()
if #dialed_number > 0 then
print("[Dialer] Calling: " .. dialed_number)
if showToast then
showToast("Calling " .. dialed_number .. "...")
end
if shellNavigateTo then
shellNavigateTo("calling")
end
else
if showToast then
showToast("Enter a number to call")
end
end
end
function callContact(name)
print("[Dialer] Calling contact: " .. name)
if showToast then
showToast("Calling " .. name .. "...")
end
if shellNavigateTo then
shellNavigateTo("calling")
end
end
function openVideoCall()
if #dialed_number > 0 then
if showToast then
showToast("Video call to " .. dialed_number)
end
else
if showToast then
showToast("Enter a number for video call")
end
end
end
print("[Dialer] Content loaded")
</script>

View File

@@ -0,0 +1,117 @@
<!-- Chat Content Fragment -->
<!-- Uses classes from components.rcss -->
<div class="app-content">
<div class="app-bar">
<div class="app-bar-nav" onclick="goBackToMessages()">
<img src="../../icons/back.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-avatar" id="chat-avatar" style="background-color: #9C27B0; width: 40px; height: 40px; margin-right: 12px;">S</div>
<div class="list-item-content" style="flex: 1;">
<span class="list-item-title" id="chat-name">Sarah</span>
<span class="list-item-subtitle" style="color: #4CAF50;">Online</span>
</div>
<div class="btn-icon" onclick="startCall()">
<img src="../../icons/phone.tga" style="width: 28px; height: 28px;"/>
</div>
</div>
<!-- Messages Area -->
<div class="chat-container" id="messages-area" style="flex: 1; overflow: auto;">
<div class="chat-timestamp">Today</div>
<div class="chat-bubble chat-bubble-received">
Hey! How are you doing?
</div>
<div class="chat-bubble chat-bubble-sent">
I'm good! Just finished some work.
</div>
<div class="chat-bubble chat-bubble-received">
Nice! Are you coming to the party tonight?
</div>
<div class="chat-bubble chat-bubble-sent">
Yeah, I'll be there around 8pm
</div>
<div class="chat-bubble chat-bubble-received">
Perfect! Can't wait to see you there!
</div>
<div class="chat-bubble chat-bubble-sent">
Same here! Should I bring anything?
</div>
<div class="chat-bubble chat-bubble-received">
Just yourself! Maybe some snacks if you want
</div>
<div class="chat-bubble chat-bubble-received">
Hey! Are you coming to the party tonight?
</div>
</div>
<!-- Input Area -->
<div class="chat-input-container">
<div class="btn-icon" onclick="attachFile()">
<img src="../../icons/add.tga" style="width: 28px; height: 28px;"/>
</div>
<input type="text" class="chat-input" id="message-input" placeholder="Message"/>
<div class="chat-send-btn" onclick="sendMessage()">
<img src="../../icons/send.tga" style="width: 28px; height: 28px;"/>
</div>
</div>
</div>
<script>
function goBackToMessages()
print("[Chat] Going back to messages")
if shellGoBack then
shellGoBack()
end
end
function startCall()
print("[Chat] Starting call")
if showToast then
showToast("Calling Sarah...")
end
if shellNavigateTo then
shellNavigateTo("calling")
end
end
function attachFile()
print("[Chat] Attach file")
if showToast then
showToast("Attach file")
end
end
function sendMessage()
local doc = shell_document
if doc then
local input = doc:GetElementById("message-input")
if input and input.value and input.value ~= "" then
local msg = input.value
print("[Chat] Sending: " .. msg)
local area = doc:GetElementById("messages-area")
if area then
local new_msg = [[<div class="chat-bubble chat-bubble-sent">]] .. msg .. [[</div>]]
area.inner_rml = area.inner_rml .. new_msg
end
input.value = ""
if showToast then
showToast("Message sent")
end
end
end
end
print("[Chat] Content loaded")
</script>

View File

@@ -1,11 +1,119 @@
<!-- Messages App Content Fragment -->
<!-- Styles are in shell.rml -->
<!-- Uses classes from components.rcss -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Messages</span>
</div>
<div class="app-body">
<span class="placeholder-text">Messages App</span>
<div class="app-bar">
<span class="app-bar-title">Messages</span>
<div class="app-bar-actions">
<div class="btn-icon" onclick="searchMessages()">
<img src="../../icons/search.tga" style="width: 28px; height: 28px;"/>
</div>
</div>
</div>
<div class="list" style="flex: 1; overflow: auto;">
<div class="list-item" onclick="openChat('Sarah')">
<div class="list-item-avatar" style="background-color: #9C27B0;">S</div>
<div class="list-item-content">
<span class="list-item-title" style="color: #BB86FC;">Sarah</span>
<span class="list-item-subtitle" style="color: #FFFFFF;">Hey! Are you coming to the party tonight?</span>
</div>
<span style="font-size: 14px; color: #888888;">2 min</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="openChat('John Smith')">
<div class="list-item-avatar" style="background-color: #3F51B5;">J</div>
<div class="list-item-content">
<span class="list-item-title">John Smith</span>
<span class="list-item-subtitle">Thanks for your help yesterday!</span>
</div>
<span style="font-size: 14px; color: #888888;">1 hr</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="openChat('Mom')">
<div class="list-item-avatar" style="background-color: #E91E63;">M</div>
<div class="list-item-content">
<span class="list-item-title" style="color: #BB86FC;">Mom</span>
<span class="list-item-subtitle" style="color: #FFFFFF;">Don't forget to call grandma!</span>
</div>
<span style="font-size: 14px; color: #888888;">3 hr</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="openChat('Work Group')">
<div class="list-item-avatar" style="background-color: #FF9800;">W</div>
<div class="list-item-content">
<span class="list-item-title">Work Group</span>
<span class="list-item-subtitle">Bob: Meeting moved to 3pm</span>
</div>
<span style="font-size: 14px; color: #888888;">Yesterday</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="openChat('Alice Johnson')">
<div class="list-item-avatar" style="background-color: #4CAF50;">A</div>
<div class="list-item-content">
<span class="list-item-title">Alice Johnson</span>
<span class="list-item-subtitle">See you at the coffee shop!</span>
</div>
<span style="font-size: 14px; color: #888888;">Yesterday</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="openChat('David Miller')">
<div class="list-item-avatar" style="background-color: #FF9800;">D</div>
<div class="list-item-content">
<span class="list-item-title">David Miller</span>
<span class="list-item-subtitle">The project is almost done</span>
</div>
<span style="font-size: 14px; color: #888888;">Monday</span>
</div>
<div class="list-divider"></div>
<div class="list-item" onclick="openChat('Emma Wilson')">
<div class="list-item-avatar" style="background-color: #E91E63;">E</div>
<div class="list-item-content">
<span class="list-item-title">Emma Wilson</span>
<span class="list-item-subtitle">That was a great movie!</span>
</div>
<span style="font-size: 14px; color: #888888;">Sunday</span>
</div>
</div>
<div class="btn-fab" onclick="newMessage()">
<img src="../../icons/edit.tga" style="width: 32px; height: 32px;"/>
</div>
</div>
<script>
function openChat(name)
print("[Messages] Opening chat: " .. name)
if shellNavigateTo then
shellNavigateTo("chat")
end
end
function searchMessages()
print("[Messages] Search")
if showToast then
showToast("Search messages")
end
end
function newMessage()
print("[Messages] New message")
if showToast then
showToast("New message")
end
end
print("[Messages] Content loaded")
</script>

View File

@@ -1,11 +1,175 @@
<!-- Music App Content Fragment -->
<!-- Styles are in shell.rml -->
<!-- Uses classes from components.rcss -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Music</span>
</div>
<div class="app-body">
<span class="placeholder-text">Music App</span>
<div class="app-content" style="position: relative;">
<div class="app-bar">
<span class="app-bar-title">Good afternoon</span>
<div class="btn-icon" onclick="openMusicSettings()">
<img src="../../icons/settings.tga" style="width: 28px; height: 28px;"/>
</div>
</div>
<div style="flex: 1; overflow: auto; padding-bottom: 100px;">
<!-- Quick Access Grid -->
<div style="display: flex; flex-wrap: wrap; gap: 8px; padding: 16px;">
<div class="quick-card" onclick="openPlaylist('liked')" style="width: 48%;">
<div class="quick-card-art" style="background-color: #7c3aed;">L</div>
<span class="quick-card-title">Liked Songs</span>
</div>
<div class="quick-card" onclick="openPlaylist('daily1')" style="width: 48%;">
<div class="quick-card-art" style="background-color: #667eea;">D</div>
<span class="quick-card-title">Daily Mix 1</span>
</div>
<div class="quick-card" onclick="openPlaylist('release')" style="width: 48%;">
<div class="quick-card-art" style="background-color: #16a34a;">R</div>
<span class="quick-card-title">Release Radar</span>
</div>
<div class="quick-card" onclick="openPlaylist('chill')" style="width: 48%;">
<div class="quick-card-art" style="background-color: #f093fb;">C</div>
<span class="quick-card-title">Chill Vibes</span>
</div>
</div>
<!-- Recently Played -->
<div class="section-header">
<span class="section-title">Recently Played</span>
<span class="section-action" onclick="showAllRecent()">SEE ALL</span>
</div>
<div style="display: flex; overflow-x: auto; padding: 0 16px; gap: 16px;">
<div class="recent-item" onclick="openPlaylist('pop')">
<div class="recent-art" style="background-color: #43e97b;">P</div>
<div class="recent-title">Pop Hits</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item" onclick="openPlaylist('electronic')">
<div class="recent-art" style="background-color: #fa709a;">E</div>
<div class="recent-title">Electronic</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item" onclick="openPlaylist('jazz')">
<div class="recent-art" style="background-color: #667eea;">J</div>
<div class="recent-title">Jazz Classics</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" onclick="showAllMixes()">SEE ALL</span>
</div>
<div class="playlist-item" onclick="openPlaylist('daily1')">
<div class="playlist-art" style="background-color: #4facfe;">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" onclick="openPlaylist('daily2')">
<div class="playlist-art" style="background-color: #43e97b;">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" onclick="openPlaylist('discover')">
<div class="playlist-art" style="background-color: #fa709a;">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" onclick="openNowPlaying()" style="position: absolute; bottom: 0; left: 0; right: 0;">
<div class="mini-player-art" id="mini-art" style="background-color: #667eea;">M</div>
<div class="mini-player-info">
<div class="mini-player-title" id="mini-title">Midnight City</div>
<div class="mini-player-artist" id="mini-artist">M83</div>
</div>
<div class="mini-player-controls">
<div class="mini-control-btn" onclick="toggleLike(); event.stopPropagation();">
<img src="../../icons/heart.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="mini-control-btn" id="play-btn" onclick="togglePlay(); event.stopPropagation();">
<img id="play-icon" src="../../icons/play.tga" style="width: 28px; height: 28px;"/>
</div>
</div>
</div>
</div>
<script>
local is_playing = false
local current_track = {
title = "Midnight City",
artist = "M83"
}
local function getDoc()
return shell_document
end
function togglePlay()
is_playing = not is_playing
local doc = getDoc()
if doc then
local icon = doc:GetElementById("play-icon")
if icon then
icon:SetAttribute("src", is_playing and "../../icons/pause.tga" or "../../icons/play.tga")
end
end
print("[Music] " .. (is_playing and "Playing" or "Paused"))
if showToast then
showToast(is_playing and "Playing" or "Paused")
end
end
function toggleLike()
print("[Music] Liked")
if showToast then
showToast("Added to Liked Songs")
end
end
function openPlaylist(id)
print("[Music] Opening playlist: " .. id)
if showToast then
showToast("Playlist: " .. id)
end
end
function openNowPlaying()
print("[Music] Now playing")
if showToast then
showToast("Now Playing: " .. current_track.title)
end
end
function showAllRecent()
print("[Music] Show all recent")
if showToast then
showToast("Recently played")
end
end
function showAllMixes()
print("[Music] Show all mixes")
if showToast then
showToast("Made for you")
end
end
function openMusicSettings()
print("[Music] Settings")
if showToast then
showToast("Music settings")
end
end
print("[Music] Content loaded")
</script>

View File

@@ -1,11 +1,309 @@
<!-- Settings App Content Fragment -->
<!-- Styles are in shell.rml -->
<!-- Uses classes from components.rcss -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Settings</span>
<div class="app-bar">
<span class="app-bar-title">Settings</span>
</div>
<div class="app-body">
<span class="placeholder-text">Settings App</span>
<div class="search-bar">
<img class="search-icon" src="../../icons/search.tga"/>
<span class="search-input">Search settings</span>
</div>
<div style="flex: 1; overflow: auto;">
<!-- Profile Card -->
<div class="list-item" onclick="openProfile()">
<div class="list-item-avatar" style="background-color: #BB86FC;">U</div>
<div class="list-item-content">
<span class="list-item-title">User</span>
<span class="list-item-subtitle">user@mosis.app</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
<div class="divider"></div>
<!-- Network Section -->
<div class="list-header">Network</div>
<div class="list-item" onclick="toggleWifi()">
<div class="list-item-icon" style="background-color: #2196F3; border-radius: 24px;">
<img src="../../icons/wifi.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Wi-Fi</span>
<span id="wifi-status" class="list-item-subtitle">Connected to MosisNetwork</span>
</div>
<div id="toggle-wifi" class="toggle active">
<div class="toggle-thumb"></div>
</div>
</div>
<div class="list-item" onclick="toggleBluetooth()">
<div class="list-item-icon" style="background-color: #3F51B5; border-radius: 24px;">
<img src="../../icons/bluetooth.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Bluetooth</span>
<span id="bt-status" class="list-item-subtitle">Off</span>
</div>
<div id="toggle-bluetooth" class="toggle">
<div class="toggle-thumb"></div>
</div>
</div>
<div class="list-item" onclick="toggleAirplane()">
<div class="list-item-icon" style="background-color: #FF9800; border-radius: 24px;">
<img src="../../icons/plane.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Airplane Mode</span>
<span class="list-item-subtitle">Off</span>
</div>
<div id="toggle-airplane" class="toggle">
<div class="toggle-thumb"></div>
</div>
</div>
<div class="divider"></div>
<!-- Device Section -->
<div class="list-header">Device</div>
<div class="list-item" onclick="openDisplay()">
<div class="list-item-icon" style="background-color: #9C27B0; border-radius: 24px;">
<img src="../../icons/display.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Display</span>
<span class="list-item-subtitle">Brightness, Dark mode</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
<div class="list-item" onclick="openSound()">
<div class="list-item-icon" style="background-color: #E91E63; border-radius: 24px;">
<img src="../../icons/volume.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Sound</span>
<span class="list-item-subtitle">Volume, Ringtone</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
<div class="list-item" onclick="openNotifications()">
<div class="list-item-icon" style="background-color: #00BCD4; border-radius: 24px;">
<img src="../../icons/notifications.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Notifications</span>
<span class="list-item-subtitle">App notifications</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
<div class="list-item" onclick="openBattery()">
<div class="list-item-icon" style="background-color: #4CAF50; border-radius: 24px;">
<img src="../../icons/battery.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Battery</span>
<span class="list-item-subtitle">85% - Not charging</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
<div class="list-item" onclick="openStorage()">
<div class="list-item-icon" style="background-color: #795548; border-radius: 24px;">
<img src="../../icons/storage.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Storage</span>
<span class="list-item-subtitle">32 GB of 128 GB used</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
<div class="divider"></div>
<!-- Privacy Section -->
<div class="list-header">Privacy</div>
<div class="list-item" onclick="toggleLocation()">
<div class="list-item-icon" style="background-color: #F44336; border-radius: 24px;">
<img src="../../icons/location.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Location</span>
<span id="location-status" class="list-item-subtitle">On - High accuracy</span>
</div>
<div id="toggle-location" class="toggle active">
<div class="toggle-thumb"></div>
</div>
</div>
<div class="list-item" onclick="openPrivacy()">
<div class="list-item-icon" style="background-color: #607D8B; border-radius: 24px;">
<img src="../../icons/lock.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">Privacy</span>
<span class="list-item-subtitle">Permissions, Data</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
<div class="divider"></div>
<!-- About Section -->
<div class="list-header">About</div>
<div class="list-item" onclick="openAbout()">
<div class="list-item-icon" style="background-color: #BB86FC; border-radius: 24px;">
<img src="../../icons/info.tga" style="width: 28px; height: 28px;"/>
</div>
<div class="list-item-content">
<span class="list-item-title">About Phone</span>
<span class="list-item-subtitle">Mosis v1.0</span>
</div>
<span class="list-item-action">&gt;</span>
</div>
</div>
</div>
<script>
local settings = {
wifi = true,
bluetooth = false,
airplane = false,
location = true
}
local function getDoc()
return shell_document
end
function toggleWifi()
settings.wifi = not settings.wifi
local doc = getDoc()
if doc then
local toggle = doc:GetElementById("toggle-wifi")
local status = doc:GetElementById("wifi-status")
if toggle then
toggle:SetClass("active", settings.wifi)
end
if status then
status.inner_rml = settings.wifi and "Connected to MosisNetwork" or "Off"
end
end
print("[Settings] Wi-Fi: " .. tostring(settings.wifi))
if showToast then
showToast(settings.wifi and "Wi-Fi enabled" or "Wi-Fi disabled")
end
end
function toggleBluetooth()
settings.bluetooth = not settings.bluetooth
local doc = getDoc()
if doc then
local toggle = doc:GetElementById("toggle-bluetooth")
local status = doc:GetElementById("bt-status")
if toggle then
toggle:SetClass("active", settings.bluetooth)
end
if status then
status.inner_rml = settings.bluetooth and "On" or "Off"
end
end
print("[Settings] Bluetooth: " .. tostring(settings.bluetooth))
if showToast then
showToast(settings.bluetooth and "Bluetooth enabled" or "Bluetooth disabled")
end
end
function toggleAirplane()
settings.airplane = not settings.airplane
local doc = getDoc()
if doc then
local toggle = doc:GetElementById("toggle-airplane")
if toggle then
toggle:SetClass("active", settings.airplane)
end
if settings.airplane then
settings.wifi = false
settings.bluetooth = false
local wt = doc:GetElementById("toggle-wifi")
local bt = doc:GetElementById("toggle-bluetooth")
if wt then wt:SetClass("active", false) end
if bt then bt:SetClass("active", false) end
end
end
print("[Settings] Airplane: " .. tostring(settings.airplane))
if showToast then
showToast(settings.airplane and "Airplane mode on" or "Airplane mode off")
end
end
function toggleLocation()
settings.location = not settings.location
local doc = getDoc()
if doc then
local toggle = doc:GetElementById("toggle-location")
local status = doc:GetElementById("location-status")
if toggle then
toggle:SetClass("active", settings.location)
end
if status then
status.inner_rml = settings.location and "On - High accuracy" or "Off"
end
end
print("[Settings] Location: " .. tostring(settings.location))
if showToast then
showToast(settings.location and "Location enabled" or "Location disabled")
end
end
function openProfile()
print("[Settings] Profile")
if showToast then showToast("User profile") end
end
function openDisplay()
print("[Settings] Display")
if showToast then showToast("Display settings") end
end
function openSound()
print("[Settings] Sound")
if showToast then showToast("Sound settings") end
end
function openNotifications()
print("[Settings] Notifications")
if showToast then showToast("Notification settings") end
end
function openBattery()
print("[Settings] Battery")
if showToast then showToast("Battery: 85%") end
end
function openStorage()
print("[Settings] Storage")
if showToast then showToast("Storage: 25% used") end
end
function openPrivacy()
print("[Settings] Privacy")
if showToast then showToast("Privacy settings") end
end
function openAbout()
print("[Settings] About")
if showToast then showToast("Mosis Virtual Phone v1.0") end
end
print("[Settings] Content loaded")
</script>