@@ -11,8 +11,9 @@ function initializeSettingsApp() {
1111 'main-settings' : 'Settings' ,
1212 'page-display' : 'Display' ,
1313 'page-sound' : 'Sound & Haptics' ,
14+ 'page-apps' : 'Apps' ,
15+ 'page-app-details' : 'App Details' ,
1416 'page-homescreen' : 'Home Screen' ,
15- 'page-live-activities' : 'Live Activities' ,
1617 'page-clock' : 'Clock' ,
1718 'page-wallpaper' : 'Wallpaper' ,
1819 'page-system' : 'System' ,
@@ -291,45 +292,128 @@ function initializeSettingsApp() {
291292 }
292293 }
293294
294- function refreshLiveActivitiesPage ( ) {
295- const container = document . getElementById ( 'live-activity-list-container' ) ;
296- const senders = JSON . parse ( window . parent . localStorage . getItem ( 'appsWithActivities' ) || '[]' ) ;
297- const blocked = JSON . parse ( window . parent . localStorage . getItem ( 'blockedActivities' ) || '[]' ) ;
298-
295+ function refreshAppsListPage ( ) {
296+ const container = document . getElementById ( 'apps-list-container' ) ;
299297 container . innerHTML = '' ;
300- if ( senders . length === 0 ) {
301- container . innerHTML = '<p style="text-align:center; opacity:0.5; padding:20px;">No activities recorded.</p>' ;
298+
299+ const perms = JSON . parse ( window . parent . localStorage . getItem ( 'appPermissions' ) || '{}' ) ;
300+ const systemAppsObj = window . parent . apps || { } ;
301+ const installedAppsObj = JSON . parse ( window . parent . localStorage . getItem ( 'userInstalledApps' ) || '{}' ) ;
302+
303+ const allApps = new Set ( [ ...Object . keys ( installedAppsObj ) , ...Object . keys ( perms ) ] ) ;
304+
305+ const excludedApps = [ 'Settings' , 'Terminal' , 'Donburi' , 'kirbStore' , 'Apps' ] ;
306+
307+ if ( allApps . size === 0 ) {
308+ container . innerHTML = '<p style="text-align:center; opacity:0.5; padding:20px;">No installed apps found.</p>' ;
302309 return ;
303310 }
304311
305- senders . forEach ( name => {
306- const item = document . createElement ( 'div' ) ;
307- item . className = 'setting-item' ;
308- const isBlocked = blocked . includes ( name ) ;
312+ allApps . forEach ( appName => {
313+ if ( excludedApps . includes ( appName ) ) return ;
309314
315+ const appDef = systemAppsObj [ appName ] || installedAppsObj [ appName ] || { } ;
316+ let iconSrc = appDef . icon || 'system.png' ;
317+ if ( ! iconSrc . startsWith ( 'http' ) && ! iconSrc . startsWith ( '/' ) && ! iconSrc . startsWith ( 'data:' ) ) {
318+ iconSrc = `/assets/appicon/${ iconSrc } ` ;
319+ }
320+
321+ const item = document . createElement ( 'div' ) ;
322+ item . className = 'setting-item nav-item' ;
310323 item . innerHTML = `
311- <div class="setting-info">
312- <span class="setting-label">${ name } </span>
324+ <div class="setting-info" style="display: flex; align-items: center; gap: 12px; flex-direction: row;">
325+ <img src="${ iconSrc } " style="width: 36px; height: 36px; border-radius: 35%; object-fit: cover; corner-shape: superellipse(1.25);">
326+ <span class="setting-label">${ appName } </span>
313327 </div>
314- <input type="checkbox" class="toggle-switch" ${ ! isBlocked ? 'checked' : '' } >
328+ <span class="material-symbols-rounded">arrow_forward_ios</span >
315329 ` ;
330+ item . onclick = ( ) => openAppDetails ( appName , appDef ) ;
331+ container . appendChild ( item ) ;
332+ } ) ;
333+ }
334+
335+ function openAppDetails ( appName , appDef ) {
336+ // Setup Header
337+ document . getElementById ( 'detail-app-name' ) . textContent = appName ;
338+ document . getElementById ( 'detail-app-url' ) . textContent = appDef . url || 'System App / Unknown URL' ;
339+
340+ let iconSrc = appDef . icon || 'system.png' ;
341+ if ( ! iconSrc . startsWith ( 'http' ) && ! iconSrc . startsWith ( '/' ) && ! iconSrc . startsWith ( 'data:' ) ) {
342+ iconSrc = `/assets/appicon/${ iconSrc } ` ;
343+ }
344+ document . getElementById ( 'detail-app-icon' ) . src = iconSrc ;
345+
346+ // Buttons
347+ document . getElementById ( 'btn-app-force-stop' ) . onclick = ( ) => {
348+ window . parent . postMessage ( { action : 'callGurasuraisuFunc' , functionName : 'forceCloseAppByName' , args : [ appName ] } , '*' ) ;
349+ Gurasuraisu . showPopup ( `Force stopped ${ appName } ` ) ;
350+ } ;
351+
352+ document . getElementById ( 'btn-app-uninstall' ) . onclick = async ( ) => {
353+ if ( await Gurasuraisu . showConfirm ( `Are you sure you want to uninstall ${ appName } ?` ) ) {
354+ Gurasuraisu . deleteApp ( appName ) ;
355+ navigateBack ( ) ;
356+ setTimeout ( refreshAppsListPage , 300 ) ;
357+ }
358+ } ;
316359
317- const toggle = item . querySelector ( 'input' ) ;
318- toggle . onchange = ( ) => {
319- let currentBlocked = JSON . parse ( window . parent . localStorage . getItem ( 'blockedActivities' ) || '[]' ) ;
320- if ( ! toggle . checked ) {
321- if ( ! currentBlocked . includes ( name ) ) currentBlocked . push ( name ) ;
322- } else {
323- currentBlocked = currentBlocked . filter ( n => n !== name ) ;
360+ document . getElementById ( 'btn-app-clear-tracking-data' ) . onclick = async ( ) => {
361+ if ( await Gurasuraisu . showConfirm ( `Clear all tracking data and revoke all permissions for ${ appName } ?` ) ) {
362+ window . parent . postMessage ( { action : 'callGurasuraisuFunc' , functionName : 'clearAppData' , args : [ appName ] } , '*' ) ;
363+ Gurasuraisu . showPopup ( `Data cleared for ${ appName } ` ) ;
364+ openAppDetails ( appName , appDef ) ; // Refresh view to update toggles
365+ }
366+ } ;
367+
368+ // Permissions List
369+ const permContainer = document . getElementById ( 'detail-app-permissions' ) ;
370+ permContainer . innerHTML = '' ;
371+
372+ const availablePerms = [
373+ { id : 'notifications' , name : 'Notifications' } ,
374+ { id : 'live-activity' , name : 'Live Activities' } ,
375+ { id : 'immersive-mode' , name : 'Immersive mode' } ,
376+ { id : 'file-upload' , name : 'File access' } ,
377+ { id : 'widgets' , name : 'Widgets' } ,
378+ { id : 'media-session' , name : 'Media session' } ,
379+ { id : 'tts' , name : 'Text-to-Speech' } ,
380+ { id : 'waves' , name : 'Waves remote' } ,
381+ { id : 'ui-sounds' , name : 'Sound effects' } ,
382+ { id : 'app-management' , name : 'App management' } ,
383+ { id : 'system-admin' , name : 'Modify core system settings (root access)' }
384+ ] ;
385+
386+ let currentPerms = JSON . parse ( window . parent . localStorage . getItem ( 'appPermissions' ) || '{}' ) ;
387+ let appPerms = currentPerms [ appName ] || { } ;
388+
389+ availablePerms . forEach ( p => {
390+ const isGranted = appPerms [ p . id ] === 'granted' ;
391+
392+ const item = document . createElement ( 'div' ) ;
393+ item . className = 'setting-item' ;
394+ item . innerHTML = `
395+ <span class="setting-label">${ p . name } </span>
396+ <input type="checkbox" class="toggle-switch" ${ isGranted ? 'checked' : '' } >
397+ ` ;
398+
399+ item . querySelector ( 'input' ) . onchange = ( e ) => {
400+ currentPerms = JSON . parse ( window . parent . localStorage . getItem ( 'appPermissions' ) || '{}' ) ;
401+ if ( ! currentPerms [ appName ] ) currentPerms [ appName ] = { } ;
402+ currentPerms [ appName ] [ p . id ] = e . target . checked ? 'granted' : 'denied' ;
403+ window . parent . localStorage . setItem ( 'appPermissions' , JSON . stringify ( currentPerms ) ) ;
404+
405+ // Real-time cleanup if a feature is revoked
406+ if ( p . id === 'live-activity' && ! e . target . checked ) {
407+ window . parent . postMessage ( { action : 'callGurasuraisuFunc' , functionName : 'stopActivitiesForApp' , args : [ appName ] } , '*' ) ;
324408 }
325- window . parent . localStorage . setItem ( 'blockedActivities' , JSON . stringify ( currentBlocked ) ) ;
326- // Notify parent to stop current activities if blocked
327- if ( ! toggle . checked ) {
328- window . parent . postMessage ( { action : 'callGurasuraisuFunc' , functionName : 'stopActivitiesForApp' , args : [ name ] } , '*' ) ;
409+ if ( p . id === 'media-session' && ! e . target . checked ) {
410+ window . parent . postMessage ( { action : 'callGurasuraisuFunc' , functionName : 'clearMediaSession' , args : [ appName ] } , '*' ) ;
329411 }
330412 } ;
331- container . appendChild ( item ) ;
413+ permContainer . appendChild ( item ) ;
332414 } ) ;
415+
416+ navigateTo ( 'page-app-details' ) ;
333417 }
334418
335419 function bindEventListeners ( ) {
@@ -348,6 +432,10 @@ function initializeSettingsApp() {
348432 if ( aboutBtn ) {
349433 aboutBtn . addEventListener ( 'click' , updateVersionDisplay ) ;
350434 }
435+ const appsListBtn = document . querySelector ( '.nav-item[data-page="page-apps"]' ) ;
436+ if ( appsListBtn ) {
437+ appsListBtn . addEventListener ( 'click' , refreshAppsListPage ) ;
438+ }
351439
352440 backBtn . addEventListener ( 'click' , navigateBack ) ;
353441
0 commit comments