feat: Flutter APK 编译成功 + Gradle 配置修复 + APK 下载部署 + 待优化清单

- 通过 qemu-user-static 实现 ARM64 主机编译 Android APK (51MB)
- 修复 Gradle: Aliyun 镜像 + PREFER_SETTINGS + JVM 内存 1536M
- 部署 APK 到 configs/downloads/, Web 下载接口已验证 (HTTP 200)
- 新增 Flutter TODO.md: 10项待优化 (P0/P1/P2 分级)
- 新增 pm_soul.md, 更新 routes.rs APK 下载路由

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
showen
2026-03-14 06:43:55 +08:00
parent bff9ec535d
commit 8ed9cb2d9d
24 changed files with 1034 additions and 20 deletions

View File

@@ -79,6 +79,14 @@ struct PlaylistSnapshot {
current_index: usize,
}
#[derive(Serialize)]
struct AppInfoResponse {
version: String,
apk_available: bool,
apk_size: Option<u64>,
download_url: &'static str,
}
pub(crate) fn build_routes(
tx: mpsc::Sender<Envelope>,
state: Arc<HttpState>,
@@ -103,6 +111,7 @@ pub(crate) fn build_routes(
let media_api = video_list_route(Arc::clone(&state))
.or(video_upload_route(Arc::clone(&state)))
.or(video_delete_route(Arc::clone(&state)))
.or(app_info_route(Arc::clone(&state)))
.or(wifi_status_route(tx.clone(), Arc::clone(&state)))
.or(wifi_scan_route(tx.clone(), Arc::clone(&state)))
.or(wifi_connect_route(tx.clone(), Arc::clone(&state)))
@@ -496,6 +505,31 @@ fn video_list_route(
})
}
fn app_info_route(
state: Arc<HttpState>,
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
warp::path!("api" / "app" / "info")
.and(warp::get())
.and(with_state(state))
.and_then(|state: Arc<HttpState>| async move {
let apk_path = downloads_dir(state.config().as_ref()).join("showen-app.apk");
let apk_size = std::fs::metadata(&apk_path)
.ok()
.filter(|meta| meta.is_file())
.map(|meta| meta.len());
Ok::<_, Infallible>(json_response(
StatusCode::OK,
&AppInfoResponse {
version: "0.1.0".to_string(),
apk_available: apk_size.is_some(),
apk_size,
download_url: "/download/showen-app.apk",
},
))
})
}
fn video_upload_route(
state: Arc<HttpState>,
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
@@ -1900,6 +1934,8 @@ const WEB_UI_HTML: &str = r##"<!doctype html>
.download-qr img{display:block;width:180px;height:180px;border-radius:12px;background:#fff}
.download-actions{display:flex;gap:10px;flex-wrap:wrap}
.download-actions>*{flex:1}
.download-meta{font-size:13px;color:var(--text);margin-bottom:14px;padding:10px 12px;border-radius:12px;background:rgba(255,255,255,.03);border:1px solid var(--border)}
.download-meta.warn{color:var(--amber);border-color:rgba(245,158,11,.3);background:rgba(245,158,11,.08)}
.download-note{font-size:12px;color:var(--muted);margin-top:12px}
.tabs{display:flex;gap:4px;background:var(--surface);border-radius:var(--radius);padding:4px;margin-bottom:16px;overflow-x:auto}
@@ -1955,7 +1991,8 @@ const WEB_UI_HTML: &str = r##"<!doctype html>
<div id="download-modal" class="modal-backdrop" onclick="closeDownloadModal(event)">
<div class="download-modal">
<h2>下载 Showen App</h2>
<p>扫描二维码或点击下载,支持蓝牙配网和远程控制</p>
<p id="download-modal-desc">扫描二维码或点击下载,支持蓝牙配网和远程控制</p>
<div id="download-meta" class="download-meta">正在获取 App 信息...</div>
<div class="download-qr"><img id="download-qr-image" alt="Showen App 下载二维码"></div>
<div class="download-actions">
<a id="download-apk-link" class="btn header-link" href="/download/showen-app.apk" download>下载 APK</a>
@@ -2136,8 +2173,43 @@ var cachedConfig=null,ws=null,wsReady=false;
var fmCurrentDir='videos',fmCurrentPath='';
function $(id){return document.getElementById(id)}
function toast(msg,err){var el=$('toast');el.textContent=msg;el.className='toast '+(err?'err':'ok');el.style.display='block';clearTimeout(el._t);el._t=setTimeout(function(){el.style.display='none'},3000)}
function appDownloadUrl(){return location.origin+'/download/showen-app.apk'}
function openDownloadModal(){var modal=$('download-modal');var url=appDownloadUrl();$('download-apk-link').href=url;$('download-qr-image').src='https://api.qrserver.com/v1/create-qr-code/?size=180x180&data='+encodeURIComponent(url);modal.classList.add('open')}
function formatBytes(size){if(size===undefined||size===null||isNaN(size))return '--';if(size<1024)return size+' B';if(size<1048576)return(size/1024).toFixed(1)+' KB';if(size<1073741824)return(size/1048576).toFixed(1)+' MB';return(size/1073741824).toFixed(1)+' GB'}
function updateDownloadModal(info){
var meta=$('download-meta'),link=$('download-apk-link'),qr=$('download-qr-image'),desc=$('download-modal-desc');
if(!info||!info.apk_available){
meta.textContent='APK 尚未发布,请稍后再试';
meta.className='download-meta warn';
link.style.display='none';
qr.style.display='none';
qr.removeAttribute('src');
desc.textContent='当前暂未提供可下载的 APK 安装包';
return;
}
var url=location.origin+(info.download_url||'/download/showen-app.apk');
meta.textContent='版本 '+(info.version||'0.1.0')+' · '+formatBytes(info.apk_size);
meta.className='download-meta';
link.style.display='inline-flex';
link.href=info.download_url||'/download/showen-app.apk';
qr.style.display='block';
qr.src='https://api.qrserver.com/v1/create-qr-code/?size=180x180&data='+encodeURIComponent(url);
desc.textContent='扫描二维码或点击下载,支持蓝牙配网和远程控制';
}
function openDownloadModal(){
var modal=$('download-modal');
$('download-meta').textContent='正在获取 App 信息...';
$('download-meta').className='download-meta';
$('download-apk-link').style.display='none';
$('download-qr-image').style.display='none';
$('download-qr-image').removeAttribute('src');
$('download-modal-desc').textContent='扫描二维码或点击下载,支持蓝牙配网和远程控制';
modal.classList.add('open');
fetch('/api/app/info').then(function(r){if(!r.ok)throw new Error('加载 App 信息失败');return r.json()}).then(updateDownloadModal).catch(function(){
$('download-meta').textContent='APK 信息加载失败,请稍后再试';
$('download-meta').className='download-meta warn';
$('download-apk-link').style.display='none';
$('download-qr-image').style.display='none';
})
}
function closeDownloadModal(ev){if(ev&&ev.target!==$('download-modal'))return;$('download-modal').classList.remove('open')}
document.addEventListener('keydown',function(ev){if(ev.key==='Escape')closeDownloadModal()})