use clap::{Args as ClapArgs, ValueEnum};
use color_eyre::eyre::Result;
use crate::shell;
use crate::{header, line, warn};
use smol::future::zip;
use smol::process::Command;
use waterui_cli::{
android::{device::AndroidDevice, platform::AndroidPlatform, toolchain::AndroidSdk},
apple::{device::AppleDevice, platform::ApplePlatform},
platform::Platform,
};
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum TargetPlatform {
Ios,
Android,
Macos,
All,
}
#[derive(ClapArgs, Debug)]
pub struct Args {
#[arg(short, long, value_enum, default_value = "all")]
platform: TargetPlatform,
}
pub async fn run(args: Args) -> Result<()> {
match args.platform {
TargetPlatform::Ios => {
let ios_devices = scan_ios_devices().await;
display_ios_devices(ios_devices);
}
TargetPlatform::Android => {
let android_result = scan_android_devices().await;
display_android_devices(android_result);
}
TargetPlatform::Macos => {
display_macos_devices();
}
TargetPlatform::All => {
let spinner = shell::spinner("Scanning devices...");
let (ios_devices, android_result) =
zip(scan_ios_devices(), scan_android_devices()).await;
if let Some(pb) = spinner {
pb.finish_and_clear();
}
display_ios_devices(ios_devices);
display_android_devices(android_result);
display_macos_devices();
}
}
Ok(())
}
async fn scan_ios_devices() -> Result<Vec<AppleDevice>, String> {
let platform = ApplePlatform::ios_simulator();
platform.scan().await.map_err(|e| e.to_string())
}
async fn scan_android_devices() -> Option<(Vec<String>, Vec<AndroidDevice>)> {
let emulator_path = AndroidSdk::emulator_path()?;
let avds_future = async {
Command::new(&emulator_path)
.arg("-list-avds")
.output()
.await
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|output| {
output
.lines()
.filter(|line| !line.is_empty())
.map(String::from)
.collect::<Vec<_>>()
})
.unwrap_or_default()
};
let devices_future = async {
let platform = AndroidPlatform::arm64();
platform.scan().await.unwrap_or_default()
};
let (avds, connected_devices) = zip(avds_future, devices_future).await;
Some((avds, connected_devices))
}
fn display_ios_devices(result: Result<Vec<AppleDevice>, String>) {
match result {
Ok(devs) => {
if !devs.is_empty() {
header!("iOS Simulators");
}
for device in &devs {
if let AppleDevice::Simulator(sim) = device {
let state_icon = if sim.state == "Booted" { "●" } else { "○" };
line!(" {} {} ({})", state_icon, sim.name, sim.udid);
}
}
if devs.is_empty() {
line!(" No iOS simulators available");
}
}
Err(e) => {
warn!("Failed to scan iOS simulators: {e}");
}
}
}
fn display_android_devices(result: Option<(Vec<String>, Vec<AndroidDevice>)>) {
let Some((avds, connected_devices)) = result else {
return;
};
header!("Android");
for avd in &avds {
let is_running = connected_devices
.iter()
.any(|d| d.identifier().starts_with("emulator-"));
let state_icon = if is_running { "●" } else { "○" };
line!(" {} {} (emulator)", state_icon, avd);
}
for device in &connected_devices {
if !device.identifier().starts_with("emulator-") {
line!(" ● {} ({})", device.identifier(), device.abi());
}
}
if avds.is_empty() && connected_devices.is_empty() {
line!(" No Android devices or emulators available");
}
}
fn display_macos_devices() {
header!("macOS");
line!(" ● Current Machine");
}