+ This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
+
+ ]]
+ }
+}
+
+-- 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 = [[
]] .. page.content .. [[
]]
+ 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
diff --git a/base-apps/com.mosis.browser/browser.rml b/base-apps/com.mosis.browser/browser.rml
index bfbba21..a3e1ec8 100644
--- a/base-apps/com.mosis.browser/browser.rml
+++ b/base-apps/com.mosis.browser/browser.rml
@@ -6,6 +6,7 @@
+
Browser
-
+
12:30
@@ -179,66 +192,52 @@
-
+
-
+
- S
-
+ S
+
-
+
-
+
-
+
-
Example Domain
+
Example Domain
This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
- More information...
-
-
-
-
Related Links
-
-
IANA - IANA-managed Reserved Domains
-
www.iana.org > domains > reserved
-
Certain domains are set aside and unavailable for registration.
-
-
-
RFC 2606 - Reserved Top Level DNS Names
-
tools.ietf.org > html > rfc2606
-
This document describes domain names reserved for documentation.
-
+ More information...
-
+
Home
-
- 1
+
+ 1Tabs
-
+
New Tab
-
+
Menu
diff --git a/base-apps/com.mosis.camera/camera.lua b/base-apps/com.mosis.camera/camera.lua
new file mode 100644
index 0000000..418aba2
--- /dev/null
+++ b/base-apps/com.mosis.camera/camera.lua
@@ -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 = [[]]
+ else
+ capture_btn.inner_rml = [[]]
+ end
+ else
+ capture_btn.inner_rml = [[]]
+ 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 = [[
+
]] .. icon .. [[
+
]] .. (is_front_camera and "Front Camera" or "Camera Preview") .. [[
+
Tap to focus
+ ]]
+ 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
diff --git a/base-apps/com.mosis.camera/camera.rml b/base-apps/com.mosis.camera/camera.rml
index 8e8b1dc..c24a3e9 100644
--- a/base-apps/com.mosis.camera/camera.rml
+++ b/base-apps/com.mosis.camera/camera.rml
@@ -6,6 +6,7 @@
+
Camera
-
+
12:30
@@ -296,13 +339,13 @@
-
+
-
+
-
+
@@ -311,7 +354,7 @@
-
+
C
Camera Preview
@@ -328,39 +371,43 @@
-
+
-
Flash: Auto
-
Timer: Off
+
Flash: Auto
+
Timer: Off
+
+
+ 00:00
+
-
-
- 1.0x
-
+
+
-
+ 1.0x
+
+
- Night
- Portrait
- Photo
- Video
- More
+ Night
+ Portrait
+ Photo
+ Video
+ More
-
+
-
+
-
+
diff --git a/base-apps/com.mosis.contacts/contacts.lua b/base-apps/com.mosis.contacts/contacts.lua
new file mode 100644
index 0000000..c79f8b7
--- /dev/null
+++ b/base-apps/com.mosis.contacts/contacts.lua
@@ -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 .. [[
+
]] .. first_letter .. [[
+ ]]
+ end
+
+ -- Get avatar color and initial
+ local color = getAvatarColor(contact.name)
+ local initial = contact.name:sub(1, 1):upper()
+
+ html = html .. [[
+
+
]] .. initial .. [[
+
+
]] .. contact.name .. [[
+
]] .. contact.phone .. [[
+
+
+
+
+
+ ]]
+ end
+
+ if #filtered_contacts == 0 then
+ html = [[
+
+
No contacts found
+
+ ]]
+ 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 .. [[
+
+
+
+
Phone
+
]] .. selected_contact.phone .. [[
+
+
+
+ ]]
+
+ -- Email
+ if selected_contact.email and selected_contact.email ~= "" then
+ html = html .. [[
+
+
+
+
Email
+
]] .. selected_contact.email .. [[
+
+
+ ]]
+ end
+
+ -- Company
+ if selected_contact.company and selected_contact.company ~= "" then
+ html = html .. [[
+
+
+
+
Company
+
]] .. selected_contact.company .. [[
+
+
+ ]]
+ 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
diff --git a/base-apps/com.mosis.contacts/contacts.rml b/base-apps/com.mosis.contacts/contacts.rml
index 2901d77..f9202ba 100644
--- a/base-apps/com.mosis.contacts/contacts.rml
+++ b/base-apps/com.mosis.contacts/contacts.rml
@@ -6,6 +6,7 @@
+
Contacts
-
+
12:30
@@ -143,162 +269,67 @@
Contacts
-
-
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
A
-
-
A
-
-
Alice Johnson
-
+1 555-0101
-
-
-
-
-
-
-
A
-
-
Andrew Smith
-
+1 555-0102
-
-
-
-
-
+
+
+
+
A
+
Contact Name
+
-
-
B
-
-
B
-
-
Bob Williams
-
+1 555-0201
-
-
-
+
+
+
+
+ Call
+
+
+
+
+ Message
+
+
+
+
+
+ Edit
+
+
-
-
C
-
-
C
-
-
Carol Davis
-
+1 555-0301
-
-
-
-
-
-
-
C
-
-
Chris Miller
-
+1 555-0302
-
-
-
-
-
+
+
+
-
-
D
-
-
D
-
-
David Brown
-
+1 555-0401
-
-
-
-
-
-
-
-
E
-
-
E
-
-
Emma Wilson
-
+1 555-0501
-
-
-
-
-
-
-
-
J
-
-
J
-
-
John Doe
-
+1 555-1234
-
-
-
-
-
-
-
-
M
-
-
M
-
-
Mary Taylor
-
+1 555-0601
-
-
-
-
-
-
-
M
-
-
Michael Lee
-
+1 555-0602
-
-
-
-
-
-
-
-
S
-
-
S
-
-
Sarah Anderson
-
+1 555-0701
-
-
-
-
+
+
+ Back to Contacts
-
+
@@ -308,7 +339,7 @@
Keypad
-
+
Recent
diff --git a/base-apps/com.mosis.dialer/calling.lua b/base-apps/com.mosis.dialer/calling.lua
new file mode 100644
index 0000000..dc819b6
--- /dev/null
+++ b/base-apps/com.mosis.dialer/calling.lua
@@ -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
diff --git a/base-apps/com.mosis.dialer/calling.rml b/base-apps/com.mosis.dialer/calling.rml
index adc1e8c..a53a1b4 100644
--- a/base-apps/com.mosis.dialer/calling.rml
+++ b/base-apps/com.mosis.dialer/calling.rml
@@ -4,45 +4,30 @@
-
+
Calling
-
-
Calling...
-
Unknown
-
+
+
+
+
A
+
Alice Johnson
+
+1 555-0101
+
Calling...
+
00:00
+
-
?
+
+
-
-
-
+
+
+
+
+ Mute
-
-
+
+
+ Keypad
+
+
+
+ Speaker
+
+
+
+ Add call
+
+
+
+ Hold
-
-
+
+
+
+
+
diff --git a/base-apps/com.mosis.dialer/dialer.lua b/base-apps/com.mosis.dialer/dialer.lua
new file mode 100644
index 0000000..40b4db0
--- /dev/null
+++ b/base-apps/com.mosis.dialer/dialer.lua
@@ -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 = 'Enter number'
+ 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 .. [[
+
+
+
+
+
+
]] .. call.name .. [[
+
]] .. call.type .. " - " .. call.time .. [[
+
+
]] .. duration_text .. [[
+
+ ]]
+ 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
diff --git a/base-apps/com.mosis.dialer/dialer.rml b/base-apps/com.mosis.dialer/dialer.rml
index c5bd8c9..2ecd71e 100644
--- a/base-apps/com.mosis.dialer/dialer.rml
+++ b/base-apps/com.mosis.dialer/dialer.rml
@@ -6,6 +6,7 @@
+
Phone
-
+
12:30
@@ -71,84 +127,96 @@
-
-
{{ dial_number }}
+
+
+
+
+ Enter number
+
-
-
-
- 1
-
+
+
+
+ 1
+
+
+
+ 2
+ ABC
+
+
+ 3
+ DEF
+
+
+ 4
+ GHI
+
+
+ 5
+ JKL
+
+
+ 6
+ MNO
+
+
+ 7
+ PQRS
+
+
+ 8
+ TUV
+
+
+ 9
+ WXYZ
+
+
+ *
+
+
+
+ 0
+ +
+
+
+ #
+
+
-
- 2
- ABC
-
-
- 3
- DEF
-
-
- 4
- GHI
-
-
- 5
- JKL
-
-
- 6
- MNO
-
-
- 7
- PQRS
-
-
- 8
- TUV
-
-
- 9
- WXYZ
-
-
- *
-
-
-
- 0
- +
-
-
- #
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
-
+
Keypad
-
+
Recent
-
+
Contacts
diff --git a/base-apps/com.mosis.messages/messages.lua b/base-apps/com.mosis.messages/messages.lua
new file mode 100644
index 0000000..e894efc
--- /dev/null
+++ b/base-apps/com.mosis.messages/messages.lua
@@ -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 = [[
]] .. conv.unread .. [[
]]
+ end
+
+ html = html .. [[
+
+
]] .. initial .. [[
+
+
+ ]] .. conv.name .. [[
+ ]] .. conv.time .. [[
+
+
]] .. conv.last_message .. [[
+
+ ]] .. unread_badge .. [[
+
+ ]]
+ 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 .. [[
+
]] .. msg.text .. [[
+ ]]
+ 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
diff --git a/base-apps/com.mosis.messages/messages.rml b/base-apps/com.mosis.messages/messages.rml
index 073ec88..acd9d65 100644
--- a/base-apps/com.mosis.messages/messages.rml
+++ b/base-apps/com.mosis.messages/messages.rml
@@ -6,6 +6,7 @@
+
Messages
-
+
12:30
@@ -97,123 +220,79 @@
-
-
-
-
+
+
+
+
+
+
+
+ Messages
+
+
+
+
+
- Messages
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
A
-
-
- Alice Johnson
- 2:34 PM
-
-
Hey! Are you coming to the party tonight?
-
-
2
+
+
+
+
+
+
-
-
-
-
B
-
-
- Bob Williams
- 1:15 PM
-
-
Thanks for the help yesterday!
-
+
J
+
+
Contact
+
Online
-
-
-
-
C
-
-
- Carol Davis
- Yesterday
-
-
The meeting has been rescheduled to Friday
-
+
+
-
-
-
-
D
-
-
- David Brown
- Yesterday
-
-
Can you send me the files?
-
-
1
-
-
-
-
-
E
-
-
- Emma Wilson
- Mon
-
-
See you at the coffee shop!
-
-
-
-
-
-
F
-
-
- Frank Miller
- Sun
-
-
Great game last night!
-
-
-
-
-
-
G
-
-
- Grace Lee
- Sat
-
-
Happy birthday! 🎂
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/base-apps/com.mosis.music/music.lua b/base-apps/com.mosis.music/music.lua
new file mode 100644
index 0000000..9bdd155
--- /dev/null
+++ b/base-apps/com.mosis.music/music.lua
@@ -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 = [[]]
+ 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 = [[]]
+ 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 .. [[
+
+
]] .. initial .. [[
+ ]] .. pl.name .. [[
+
+ ]]
+ 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 .. [[
+
+
]] .. initial .. [[
+
]] .. item.name .. [[
+
]] .. item.type .. [[
+
+ ]]
+ 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
diff --git a/base-apps/com.mosis.music/music.rml b/base-apps/com.mosis.music/music.rml
index cf27378..df17cb1 100644
--- a/base-apps/com.mosis.music/music.rml
+++ b/base-apps/com.mosis.music/music.rml
@@ -6,20 +6,30 @@
+
Music
-
+
12:30
@@ -278,7 +311,7 @@
Music
-
+
@@ -292,28 +325,28 @@
-
-
+
+
L
Liked Songs
-
+
D
Daily Mix 1
-
+
R
Release Radar
-
+
C
Chill Vibes
-
+
W
Workout Mix
-
+
F
Focus Flow
@@ -325,23 +358,23 @@
SEE ALL
-
-
+
+
P
Pop Hits
Playlist
-
+
E
Electronic
Playlist
-
+
J
Jazz Classics
Playlist
-
+
R
Rock Legends
Playlist
@@ -354,7 +387,7 @@
SEE ALL
-
+
1
Daily Mix 1
@@ -362,7 +395,7 @@
-
+
2
Daily Mix 2
@@ -370,7 +403,7 @@
-
+
D
Discover Weekly
@@ -380,17 +413,20 @@
-
-
M
+
+
+
+
+
M
-
Midnight City
-
M83
+
Midnight City
+
M83
-
+
-
+
@@ -402,11 +438,11 @@
Home
-
+
Search
-
+
Library
diff --git a/base-apps/com.mosis.settings/settings.lua b/base-apps/com.mosis.settings/settings.lua
new file mode 100644
index 0000000..c8660eb
--- /dev/null
+++ b/base-apps/com.mosis.settings/settings.lua
@@ -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
diff --git a/base-apps/com.mosis.settings/settings.rml b/base-apps/com.mosis.settings/settings.rml
index db5c398..2fd1b6a 100644
--- a/base-apps/com.mosis.settings/settings.rml
+++ b/base-apps/com.mosis.settings/settings.rml
@@ -6,6 +6,7 @@
+
Settings
-
+