From fe7edfea3f5ca40897b4aabcdbf55168550c4a92 Mon Sep 17 00:00:00 2001 From: toorux Date: Wed, 7 Oct 2026 00:35:33 +0800 Subject: [PATCH] refactor(apk): move global preferences into dedicated settings page --- docs/user-guide/apk-manager.md | 2 ++ docs/zh-CN/user-guide/apk-manager.md | 2 ++ native/plugin_navigation.h | 2 +- src/session.rs | 1 + tests/apk_manager_fixture.tsx | 1 + tests/plugin_navigation.cpp | 2 +- ui/src/apk-manager.tsx | 5 +++-- ui/src/main.tsx | 4 ++-- ui/src/manager-switcher.tsx | 2 +- 9 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/user-guide/apk-manager.md b/docs/user-guide/apk-manager.md index 08ce5c2..23cc758 100644 --- a/docs/user-guide/apk-manager.md +++ b/docs/user-guide/apk-manager.md @@ -1,5 +1,7 @@ # APK Manager +Global controller and container lifecycle preferences are under **APK → Settings**, separate from the application list. Per-application display and launch preferences remain in each app’s management dialog. + [简体中文](../zh-CN/user-guide/apk-manager.md) [Documentation](../README.md) · [Launcher](../launcher.md) diff --git a/docs/zh-CN/user-guide/apk-manager.md b/docs/zh-CN/user-guide/apk-manager.md index eb26f41..e733a97 100644 --- a/docs/zh-CN/user-guide/apk-manager.md +++ b/docs/zh-CN/user-guide/apk-manager.md @@ -1,5 +1,7 @@ # APK 管理 +全局手柄与容器生命周期选项位于 **APK → 设置**,不再占用应用列表。每个应用的显示与启动选项仍保留在对应应用的管理弹窗内。 + [English](../../user-guide/apk-manager.md) [文档首页](../README.md) · [启动台](../launcher.md) diff --git a/native/plugin_navigation.h b/native/plugin_navigation.h index e142695..cd77cfe 100644 --- a/native/plugin_navigation.h +++ b/native/plugin_navigation.h @@ -21,5 +21,5 @@ inline bool allow_plugin_navigation(const std::string& url, const std::string& o } inline bool manager_navigation_page(const std::string& page) { - return page=="installed"||page=="catalog"||page=="sources"||page=="notification-settings"||page=="settings"||page=="launcher-settings"||page=="updates"||page=="about"||page=="terminal"||page=="files"||page=="apk"||page=="apk-containers"||page=="apk-cleanup"; + return page=="installed"||page=="catalog"||page=="sources"||page=="notification-settings"||page=="settings"||page=="launcher-settings"||page=="updates"||page=="about"||page=="terminal"||page=="files"||page=="apk"||page=="apk-containers"||page=="apk-cleanup"||page=="apk-settings"; } diff --git a/src/session.rs b/src/session.rs index 4ab4bf9..1cf4b66 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1140,6 +1140,7 @@ impl Agent { | "notification-settings" | "launcher-settings" | "apk" + | "apk-settings" ) ) { diff --git a/tests/apk_manager_fixture.tsx b/tests/apk_manager_fixture.tsx index c087c81..24ef69f 100644 --- a/tests/apk_manager_fixture.tsx +++ b/tests/apk_manager_fixture.tsx @@ -23,6 +23,7 @@ const button=(text:string,scope:ParentNode=document)=>Array.from(scope.querySele async function snapshot(name:string){await wait(220);console.log('FRAMELY_PREVIEW_INSTALL_'+name);await wait(180);} (async()=>{ configureLanguage('en-US',[]);render();await until(()=>document.querySelectorAll('.apk-row').length===2);const content=document.querySelector('.content')!.getBoundingClientRect(),manager=document.querySelector('.apk-manager')!.getBoundingClientRect();const padding=getComputedStyle(document.querySelector('.content')!);if(Math.abs(manager.width-(document.querySelector('.content')!.clientWidth-parseFloat(padding.paddingLeft)-parseFloat(padding.paddingRight)))>2)throw Error(`Manager width does not fill content: ${manager.width}/${content.width}, ${padding.paddingLeft}/${padding.paddingRight}`);const search=document.querySelector('.apk-filters input')!.getBoundingClientRect(),filter=document.querySelector('.apk-filters .framely-select>button')!.getBoundingClientRect();if(window.innerWidth>700?(Math.abs(search.y-filter.y)>1||Math.abs(search.height-filter.height)>1||search.right>=filter.left):(filter.top1))throw Error('APK search/filter controls are not aligned');await snapshot('APK_EN'); + if(document.querySelector('[role=switch]'))throw Error('Global settings leaked onto app list');button('Settings',document.querySelector('.apk-tabs')!).click();await until(()=>!!document.querySelector('[role=switch]')); const automatic=()=>document.querySelector('[role=switch][aria-label="Stop container after app exits"]')!; const closePolicy=()=>document.querySelector('[role=switch][aria-label="Stop container when closing its window"]')!; if(automatic().getAttribute('aria-checked')!=='false'||closePolicy().getAttribute('aria-checked')!=='true')throw Error('Incorrect container lifecycle defaults'); diff --git a/tests/plugin_navigation.cpp b/tests/plugin_navigation.cpp index 8c236a5..80c339d 100644 --- a/tests/plugin_navigation.cpp +++ b/tests/plugin_navigation.cpp @@ -1,7 +1,7 @@ #include "plugin_navigation.h" #include int main() { - for (const auto& page : {"settings","updates","about","terminal","files","apk-containers"}) assert(manager_navigation_page(page)); + for (const auto& page : {"settings","updates","about","terminal","files","apk-containers","apk-settings"}) assert(manager_navigation_page(page)); assert(!manager_navigation_page("unknown")); const std::string origin="http://localhost:19626"; assert(allow_plugin_navigation(origin+"/window",origin,true,true)); diff --git a/ui/src/apk-manager.tsx b/ui/src/apk-manager.tsx index de14ae7..457d077 100644 --- a/ui/src/apk-manager.tsx +++ b/ui/src/apk-manager.tsx @@ -49,8 +49,9 @@ export function ApkManager({view:externalView,onViewChange}:{view?:string;onView {if(e.target.files?.[0])void inspect(e.target.files[0]);}}/> {!data.available&&

{t('未检测到 Lepton,请先通过 Steam 安装。')}

} {notice&&

{notice}

}{error&&
{error}
} - {!externalView&&
{[['apps','应用'],['containers','容器'],['cleanup','数据清理']].map(([key,label])=>)}
} - {view==='apps'&&<>void perform('lifecycle.settings',{autoStopContainer,stopContainerOnClose:data.stopContainerOnClose??true})}/>void perform('lifecycle.settings',{autoStopContainer:!!data.autoStopContainer,stopContainerOnClose})}/>void perform('gamepad.settings',{enabled})}/>
setQuery(e.target.value)}/>setQuery(e.target.value)}/>setPath(e.target.value)}/>setContextName(e.target.value)}/>
{data.roots.map(p=>
{p}
)}} {view==='cleanup'&&<>

{t('数据清理')}

{t('存档、设置与恢复备份默认不选中。未知归属的数据不会自动删除。')}

{cleanup.map(i=>
setSelected(s=>checked?[...s,i.id]:s.filter(v=>v!==i.id))}/>
{i.name}

{t(i.kind==='unknown'?'未知归属的数据':i.kind==='data'?'保留的应用数据':i.kind==='backups'?'恢复备份':(i.kind==='reviews'||i.kind==='uploads')?'过期上传文件':'旧 APK')} · {bytes(i.bytes)}

{i.unknown?{t('无法确认归属,不提供自动删除。')}:i.sensitive&&{t('删除后不可恢复')}}
{i.restorable&&}
)}{!cleanup.length&&

{t('没有可清理的数据。')}

}} {!!data.warnings.length&&
{t('部分容器无法读取')}{data.warnings.map((w,i)=>

{w}

)}
} diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 0594d7a..a20e138 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -93,7 +93,7 @@ function App(){ if(parts[1]==='notifications')return ; if(windowPlugin){const p=status.database.plugins[windowPlugin];return
{p?.enabled&&!status.database.safeMode?:

{t("插件已停用或正在启动。")}

}{error&&

{error}

}
;} return
{managerMode?<>{managerSection==='plugins'&&}:<>}
{error&&

{error}

}{status.database.safeMode&&

{t("安全模式:第三方插件暂停运行。")}

} - {selected?<>
{selected.manifest.name}{managerMode&&}
{selected.enabled&&!status.database.safeMode&&selected.manifest.ui.quickPage?:

{t("插件已停用、处于安全模式或没有快捷页面。")}

}:
+ {selected?<>
{selected.manifest.name}{managerMode&&}
{selected.enabled&&!status.database.safeMode&&selected.manifest.ui.quickPage?:

{t("插件已停用、处于安全模式或没有快捷页面。")}

}:
{(tab==='favorites'||tab==='installed')&&<> {managerMode&&

{t("已安装")}

{plugins.length}{t("个插件,")}{plugins.filter(p=>p.enabled).length}{t("个已启用")}

setQuery(e.target.value)}/>
} {managerMode&&
{t("插件")}{t("状态")}{t("版本")}{t("操作")}
}
{listedPlugins.map(p=>
@@ -106,7 +106,7 @@ function App(){ {tab==='notifications'&&!managerMode&&<>}/>{readError&&

{readError}

}} {managerMode&&(filesVisited||tab==='files')&&} {tab==='terminal'&&managerMode&&} - {apkPanel&&setTab(view==='apps'?'apk':'apk-'+view)}/>} + {apkPanel&&setTab(view==='apps'?'apk':'apk-'+view)}/>} {tab==='launcher-settings'&&managerMode&&<>

{t('启动台')}

{t('启动方式、菜单与分类。')}

} {tab==='notification-settings'&&managerMode&&} {tab==='sources'&&managerMode&&<>

{t("插件源")}

{t("管理插件目录及订阅。")}

{await api('sources.save',{sources:s});await refresh();}}/>
} diff --git a/ui/src/manager-switcher.tsx b/ui/src/manager-switcher.tsx index 5a5e4f4..c000aff 100644 --- a/ui/src/manager-switcher.tsx +++ b/ui/src/manager-switcher.tsx @@ -6,7 +6,7 @@ export function sectionFor(tab:string):ManagerSection { if(tab==='terminal'||tab==='files')return tab; return ['settings','launcher-settings','about','updates'].includes(tab)?'settings':'plugins'; } -export function managerTab(hash:string){const key=hash.slice(1);return ['installed','catalog','sources','notification-settings','settings','launcher-settings','about','updates','terminal','files','apk','apk-containers','apk-cleanup'].includes(key)?key:'installed';} +export function managerTab(hash:string){const key=hash.slice(1);return ['installed','catalog','sources','notification-settings','settings','launcher-settings','about','updates','terminal','files','apk','apk-containers','apk-cleanup','apk-settings'].includes(key)?key:'installed';} export function ManagerSwitcher({section,onChange}:{section:ManagerSection;onChange:(section:ManagerSection)=>void}) { return
{([['plugins','插件'],['apk','APK'],['terminal','终端'],['files','文件'],['settings','设置']] as const).map(([key,label])=>)}