Skip to content

Commit b4f4745

Browse files
WayneWayne
authored andcommitted
feat: 完善ios选择
1 parent fdb68c5 commit b4f4745

6 files changed

Lines changed: 258 additions & 39 deletions

File tree

apps/simdock-desktop/src/i18n.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,13 @@ pub(crate) fn cleanup_dialog_title(platform: Platform, language: AppLanguage) ->
312312
pub(crate) fn cleanup_dialog_detail(platform: Platform, language: AppLanguage) -> &'static str {
313313
match (platform, language) {
314314
(Platform::Ios, AppLanguage::English) => {
315-
"Choose Simdock-managed iOS cache and recovery data to remove. Xcode and Apple-managed runtimes are not deleted."
315+
"Choose Simdock cache, recovery data, and installed iOS simulator devices to remove. Xcode and Apple-managed runtimes are not deleted."
316316
}
317317
(Platform::Android, AppLanguage::English) => {
318318
"Choose Simdock-managed Android files to remove. SDK, AVD, and Java runtime choices may require reinstalling later."
319319
}
320320
(Platform::Ios, AppLanguage::Chinese) => {
321-
"选择要移除的Simdock托管iOS缓存和恢复数据。不会删除Xcode或Apple管理的运行时。"
321+
"选择要移除的Simdock缓存、恢复数据和已安装iOS模拟器设备。不会删除Xcode或Apple管理的运行时。"
322322
}
323323
(Platform::Android, AppLanguage::Chinese) => {
324324
"选择要移除的Simdock托管Android文件。SDK、AVD和Java运行时清理后可能需要重新安装。"
@@ -327,7 +327,7 @@ pub(crate) fn cleanup_dialog_detail(platform: Platform, language: AppLanguage) -
327327
}
328328

329329
pub(crate) fn cleanup_item_label(
330-
kind: crate::CleanupItemKind,
330+
kind: &crate::CleanupItemKind,
331331
language: AppLanguage,
332332
) -> &'static str {
333333
match (kind, language) {
@@ -337,6 +337,12 @@ pub(crate) fn cleanup_item_label(
337337
"Install logs and recovery snapshot"
338338
}
339339
(crate::CleanupItemKind::LogsAndSnapshot, AppLanguage::Chinese) => "安装日志和恢复快照",
340+
(crate::CleanupItemKind::IosSimulatorDevice { .. }, AppLanguage::English) => {
341+
"iOS simulator device"
342+
}
343+
(crate::CleanupItemKind::IosSimulatorDevice { .. }, AppLanguage::Chinese) => {
344+
"iOS模拟器设备"
345+
}
340346
(crate::CleanupItemKind::ManagedJavaRuntime, AppLanguage::English) => {
341347
"Managed Java runtime"
342348
}

apps/simdock-desktop/src/main.rs

Lines changed: 104 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use iced::{
2323
};
2424
use serde::{Deserialize, Serialize};
2525
use simdock_core::{
26-
DoctorReport, InstallRequest, Platform, TaskEvent,
26+
DoctorReport, InstallRequest, Platform, SimulatorDevice, TaskEvent,
2727
provider::{PlatformProvider, android::AndroidProvider, ios::IosProvider},
2828
service::SimdockService,
2929
};
@@ -179,13 +179,15 @@ struct CleanupDialog {
179179
#[derive(Debug, Clone)]
180180
struct CleanupItemView {
181181
kind: CleanupItemKind,
182+
label: String,
182183
checked: bool,
183184
}
184185

185-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186+
#[derive(Debug, Clone, PartialEq, Eq)]
186187
pub(crate) enum CleanupItemKind {
187188
DownloadCache,
188189
LogsAndSnapshot,
190+
IosSimulatorDevice { udid: String },
189191
ManagedJavaRuntime,
190192
AndroidVirtualDevices,
191193
AndroidSdk,
@@ -207,6 +209,7 @@ enum Message {
207209
OpenSimulatorRequested(Platform),
208210
OpenSimulatorFinished(Platform, Result<(), String>),
209211
ManageInstalledContentRequested(Platform),
212+
CleanupDialogPrepared(Result<CleanupDialog, String>),
210213
CleanupItemToggled(CleanupItemKind, bool),
211214
CleanupCancelled,
212215
CleanupConfirmed,
@@ -333,32 +336,17 @@ impl InstallTasks {
333336
}
334337

335338
impl CleanupDialog {
336-
fn new(platform: Platform) -> Self {
339+
fn new(platform: Platform, language: AppLanguage) -> Self {
337340
let mut items = vec![
338-
CleanupItemView {
339-
kind: CleanupItemKind::DownloadCache,
340-
checked: true,
341-
},
342-
CleanupItemView {
343-
kind: CleanupItemKind::LogsAndSnapshot,
344-
checked: true,
345-
},
341+
cleanup_item_view(CleanupItemKind::DownloadCache, language, true),
342+
cleanup_item_view(CleanupItemKind::LogsAndSnapshot, language, true),
346343
];
347344

348345
if platform == Platform::Android {
349346
items.extend([
350-
CleanupItemView {
351-
kind: CleanupItemKind::ManagedJavaRuntime,
352-
checked: false,
353-
},
354-
CleanupItemView {
355-
kind: CleanupItemKind::AndroidVirtualDevices,
356-
checked: false,
357-
},
358-
CleanupItemView {
359-
kind: CleanupItemKind::AndroidSdk,
360-
checked: false,
361-
},
347+
cleanup_item_view(CleanupItemKind::ManagedJavaRuntime, language, false),
348+
cleanup_item_view(CleanupItemKind::AndroidVirtualDevices, language, false),
349+
cleanup_item_view(CleanupItemKind::AndroidSdk, language, false),
362350
]);
363351
}
364352

@@ -370,11 +358,22 @@ impl CleanupDialog {
370358
}
371359
}
372360

361+
fn add_ios_simulator_devices(&mut self, devices: Vec<SimulatorDevice>) {
362+
self.items.extend(devices.into_iter().map(|device| {
363+
let label = device.display_label();
364+
CleanupItemView {
365+
kind: CleanupItemKind::IosSimulatorDevice { udid: device.id },
366+
label,
367+
checked: false,
368+
}
369+
}));
370+
}
371+
373372
fn selected_items(&self) -> Vec<CleanupItemKind> {
374373
self.items
375374
.iter()
376375
.filter(|item| item.checked)
377-
.map(|item| item.kind)
376+
.map(|item| item.kind.clone())
378377
.collect()
379378
}
380379

@@ -383,6 +382,18 @@ impl CleanupDialog {
383382
}
384383
}
385384

385+
fn cleanup_item_view(
386+
kind: CleanupItemKind,
387+
language: AppLanguage,
388+
checked: bool,
389+
) -> CleanupItemView {
390+
CleanupItemView {
391+
label: i18n::cleanup_item_label(&kind, language).to_string(),
392+
kind,
393+
checked,
394+
}
395+
}
396+
386397
impl InstallTaskView {
387398
fn idle(platform: Platform) -> Self {
388399
Self {
@@ -776,7 +787,22 @@ fn update(app: &mut DoctorApp, message: Message) -> Task<Message> {
776787
}
777788
Message::ManageInstalledContentRequested(platform) => {
778789
if !app.install_task(platform).is_busy() {
779-
app.cleanup_dialog = Some(CleanupDialog::new(platform));
790+
app.cleanup_dialog = None;
791+
let language = app.language;
792+
return Task::perform(prepare_cleanup_dialog(platform, language), |result| {
793+
Message::CleanupDialogPrepared(result)
794+
});
795+
}
796+
Task::none()
797+
}
798+
Message::CleanupDialogPrepared(result) => {
799+
match result {
800+
Ok(dialog) => app.cleanup_dialog = Some(dialog),
801+
Err(error) => {
802+
let mut dialog = CleanupDialog::new(app.active_tab, app.language);
803+
dialog.error = Some(i18n::cleanup_failed_message(&error, app.language));
804+
app.cleanup_dialog = Some(dialog);
805+
}
780806
}
781807
Task::none()
782808
}
@@ -1202,17 +1228,32 @@ fn cleanup_dialog_overlay<'a>(
12021228
) -> Element<'a, Message> {
12031229
let mut checklist = column![].spacing(12);
12041230
for item in &dialog.items {
1205-
let kind = item.kind;
1206-
let mut row = checkbox(i18n::cleanup_item_label(kind, language), item.checked)
1231+
let kind = item.kind.clone();
1232+
let mut row = checkbox(item.label.clone(), item.checked)
12071233
.text_size(14)
12081234
.size(18);
12091235

12101236
if !dialog.running {
1211-
row = row.on_toggle(move |checked| Message::CleanupItemToggled(kind, checked));
1237+
row = row.on_toggle(move |checked| Message::CleanupItemToggled(kind.clone(), checked));
12121238
}
12131239

12141240
checklist = checklist.push(row);
12151241
}
1242+
let checklist_height = if dialog.items.len() > 8 {
1243+
Length::Fixed(260.0)
1244+
} else {
1245+
Length::Shrink
1246+
};
1247+
let checklist = container(
1248+
scrollable(checklist.width(Length::Fill))
1249+
.height(checklist_height)
1250+
.direction(Direction::Vertical(
1251+
Scrollbar::new().width(18).scroller_width(8).margin(0),
1252+
))
1253+
.style(main_scrollbar_style),
1254+
)
1255+
.width(Length::Fill)
1256+
.height(checklist_height);
12161257

12171258
let mut confirm_button = button(text(i18n::cleanup_confirm_button_label(language)).size(14))
12181259
.padding([10, 16])
@@ -1262,7 +1303,7 @@ fn cleanup_dialog_overlay<'a>(
12621303
container(
12631304
container(content)
12641305
.padding(24)
1265-
.width(520)
1306+
.width(640)
12661307
.style(cleanup_dialog_card_style),
12671308
)
12681309
.width(Length::Fill)
@@ -2137,6 +2178,23 @@ fn load_doctor_task() -> Task<Message> {
21372178
Task::perform(load_doctor_snapshot(), Message::DoctorLoaded)
21382179
}
21392180

2181+
async fn prepare_cleanup_dialog(
2182+
platform: Platform,
2183+
language: AppLanguage,
2184+
) -> Result<CleanupDialog, String> {
2185+
let mut dialog = CleanupDialog::new(platform, language);
2186+
2187+
if platform == Platform::Ios {
2188+
let devices = IosProvider::new()
2189+
.list_simulator_devices()
2190+
.await
2191+
.map_err(|error| error.to_string())?;
2192+
dialog.add_ios_simulator_devices(devices);
2193+
}
2194+
2195+
Ok(dialog)
2196+
}
2197+
21402198
async fn cleanup_selected_items(
21412199
platform: Platform,
21422200
items: Vec<CleanupItemKind>,
@@ -2145,12 +2203,23 @@ async fn cleanup_selected_items(
21452203
let mut removed_paths = 0;
21462204

21472205
for item in items {
2148-
for path in cleanup_paths_for_item(platform, item, &paths) {
2149-
if remove_known_path(&path)
2150-
.map_err(|error| format!("failed to remove {}: {error}", path.to_string_lossy()))?
2151-
{
2206+
match &item {
2207+
CleanupItemKind::IosSimulatorDevice { udid } => {
2208+
IosProvider::new()
2209+
.delete_simulator_device(udid)
2210+
.await
2211+
.map_err(|error| error.to_string())?;
21522212
removed_paths += 1;
21532213
}
2214+
_ => {
2215+
for path in cleanup_paths_for_item(platform, &item, &paths) {
2216+
if remove_known_path(&path).map_err(|error| {
2217+
format!("failed to remove {}: {error}", path.to_string_lossy())
2218+
})? {
2219+
removed_paths += 1;
2220+
}
2221+
}
2222+
}
21542223
}
21552224
}
21562225

@@ -2159,7 +2228,7 @@ async fn cleanup_selected_items(
21592228

21602229
fn cleanup_paths_for_item(
21612230
platform: Platform,
2162-
item: CleanupItemKind,
2231+
item: &CleanupItemKind,
21632232
paths: &AppPaths,
21642233
) -> Vec<PathBuf> {
21652234
match item {
@@ -2174,6 +2243,7 @@ fn cleanup_paths_for_item(
21742243
paths.logs_dir.clone(),
21752244
paths.app_support_dir.join(OPERATION_SNAPSHOT_FILE),
21762245
],
2246+
CleanupItemKind::IosSimulatorDevice { .. } => Vec::new(),
21772247
CleanupItemKind::ManagedJavaRuntime => vec![paths.app_support_dir.join("java-runtime")],
21782248
CleanupItemKind::AndroidVirtualDevices => vec![paths.android_avd_root.clone()],
21792249
CleanupItemKind::AndroidSdk => vec![paths.android_sdk_root.clone()],

crates/simdock-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ pub mod service;
99

1010
pub use model::{
1111
CreateProfileRequest, DeviceTemplate, DoctorCheck, DoctorReport, InstallRequest, Instance,
12-
InstanceState, Platform, Profile, Runtime, TaskEvent, TaskState,
12+
InstanceState, Platform, Profile, Runtime, SimulatorDevice, TaskEvent, TaskState,
1313
};

crates/simdock-core/src/model/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod platform;
55
mod profile;
66
mod requests;
77
mod runtime;
8+
mod simulator_device;
89
mod task;
910

1011
pub use device_template::DeviceTemplate;
@@ -14,4 +15,5 @@ pub use platform::Platform;
1415
pub use profile::Profile;
1516
pub use requests::{CreateProfileRequest, InstallRequest};
1617
pub use runtime::Runtime;
18+
pub use simulator_device::SimulatorDevice;
1719
pub use task::{TaskEvent, TaskState};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
use crate::model::Platform;
4+
5+
#[derive(Debug, Clone, Serialize, Deserialize)]
6+
/// 一个已存在的模拟器设备。
7+
///
8+
/// 该模型描述系统当前可见的设备实例,不区分是否由Simdock创建。
9+
pub struct SimulatorDevice {
10+
pub id: String,
11+
pub platform: Platform,
12+
pub name: String,
13+
pub runtime_id: String,
14+
pub runtime_name: String,
15+
pub runtime_version: String,
16+
pub state: String,
17+
pub available: bool,
18+
}
19+
20+
impl SimulatorDevice {
21+
/// 返回用于UI选择列表的紧凑展示名。
22+
///
23+
/// 示例:`iPhone 16/iOS 26.4/Shutdown`。
24+
pub fn display_label(&self) -> String {
25+
format!(
26+
"{}/{}/{}",
27+
self.normalized_device_name(),
28+
self.runtime_name,
29+
self.state
30+
)
31+
}
32+
33+
/// 规范化设备名,避免Simdock早期创建的设备重复展示runtime。
34+
fn normalized_device_name(&self) -> String {
35+
self.name
36+
.strip_prefix("Simdock ")
37+
.unwrap_or(&self.name)
38+
.strip_suffix(&format!(" {}", self.runtime_name))
39+
.unwrap_or_else(|| self.name.strip_prefix("Simdock ").unwrap_or(&self.name))
40+
.to_string()
41+
}
42+
}

0 commit comments

Comments
 (0)