diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 12ff0b0..81f4eae 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -65,6 +65,10 @@ dependencies {
// DataStore for preferences
implementation(libs.androidx.datastore.preferences)
+ // Coil for image loading (SVG support)
+ implementation(libs.coil.compose)
+ implementation(libs.coil.svg)
+
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
diff --git a/app/src/main/assets/osicons/archlinux.svg b/app/src/main/assets/osicons/archlinux.svg
new file mode 100644
index 0000000..ec9b385
--- /dev/null
+++ b/app/src/main/assets/osicons/archlinux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/debian.svg b/app/src/main/assets/osicons/debian.svg
new file mode 100644
index 0000000..6e4be6a
--- /dev/null
+++ b/app/src/main/assets/osicons/debian.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/fedora.svg b/app/src/main/assets/osicons/fedora.svg
new file mode 100644
index 0000000..7f461d0
--- /dev/null
+++ b/app/src/main/assets/osicons/fedora.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/freebsd.svg b/app/src/main/assets/osicons/freebsd.svg
new file mode 100644
index 0000000..8febe9d
--- /dev/null
+++ b/app/src/main/assets/osicons/freebsd.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/linuxmint.svg b/app/src/main/assets/osicons/linuxmint.svg
new file mode 100644
index 0000000..af08bf3
--- /dev/null
+++ b/app/src/main/assets/osicons/linuxmint.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/nixos.svg b/app/src/main/assets/osicons/nixos.svg
new file mode 100644
index 0000000..40e5de8
--- /dev/null
+++ b/app/src/main/assets/osicons/nixos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/opnsense.svg b/app/src/main/assets/osicons/opnsense.svg
new file mode 100644
index 0000000..53a38d7
--- /dev/null
+++ b/app/src/main/assets/osicons/opnsense.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/popos.svg b/app/src/main/assets/osicons/popos.svg
new file mode 100644
index 0000000..faf0e13
--- /dev/null
+++ b/app/src/main/assets/osicons/popos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/tails.svg b/app/src/main/assets/osicons/tails.svg
new file mode 100644
index 0000000..729a51d
--- /dev/null
+++ b/app/src/main/assets/osicons/tails.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/assets/osicons/ubuntu.svg b/app/src/main/assets/osicons/ubuntu.svg
new file mode 100644
index 0000000..227f86a
--- /dev/null
+++ b/app/src/main/assets/osicons/ubuntu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/main/java/sh/sar/isodroid/MainActivity.kt b/app/src/main/java/sh/sar/isodroid/MainActivity.kt
index 660ae51..937a007 100644
--- a/app/src/main/java/sh/sar/isodroid/MainActivity.kt
+++ b/app/src/main/java/sh/sar/isodroid/MainActivity.kt
@@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import sh.sar.isodroid.ui.screens.DownloadsScreen
import sh.sar.isodroid.ui.screens.MainScreen
import sh.sar.isodroid.ui.screens.SettingsScreen
import sh.sar.isodroid.ui.theme.ISODroidTheme
@@ -133,11 +134,21 @@ fun ISODroidNavHost(viewModel: MainViewModel) {
composable("main") {
MainScreen(
viewModel = viewModel,
+ onNavigateToDownloads = {
+ navController.navigate("downloads")
+ },
onNavigateToSettings = {
navController.navigate("settings")
}
)
}
+ composable("downloads") {
+ DownloadsScreen(
+ onNavigateBack = {
+ navController.popBackStack()
+ }
+ )
+ }
composable("settings") {
SettingsScreen(
viewModel = viewModel,
diff --git a/app/src/main/java/sh/sar/isodroid/ui/screens/DownloadsScreen.kt b/app/src/main/java/sh/sar/isodroid/ui/screens/DownloadsScreen.kt
new file mode 100644
index 0000000..8a08a43
--- /dev/null
+++ b/app/src/main/java/sh/sar/isodroid/ui/screens/DownloadsScreen.kt
@@ -0,0 +1,256 @@
+package sh.sar.isodroid.ui.screens
+
+import android.content.Intent
+import android.net.Uri
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Download
+import androidx.compose.material.icons.filled.OpenInNew
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import coil.decode.SvgDecoder
+import coil.request.ImageRequest
+
+data class OsDownload(
+ val name: String,
+ val description: String,
+ val url: String,
+ val icon: String? = null
+)
+
+private val osDownloads = listOf(
+ OsDownload(
+ name = "Ubuntu",
+ description = "",
+ url = "https://ubuntu.com/download",
+ icon = "ubuntu.svg"
+ ),
+ OsDownload(
+ name = "Debian",
+ description = "Debian is a complete Free Operating System!",
+ url = "https://www.debian.org/download",
+ icon = "debian.svg"
+ ),
+ OsDownload(
+ name = "Arch Linux",
+ description = "A simple, lightweight distribution",
+ url = "https://archlinux.org/download/",
+ icon = "archlinux.svg"
+ ),
+ OsDownload(
+ name = "NixOS",
+ description = "Declarative builds and deployments.",
+ url = "https://nixos.org/download/#nixos-iso",
+ icon = "nixos.svg"
+ ),
+ OsDownload(
+ name = "OPNsense",
+ description = "OPNsense® is an open source, feature rich firewall and routing platform, offering cutting-edge network protection.",
+ url = "https://opnsense.org/download/",
+ icon = "opnsense.svg"
+ ),
+ OsDownload(
+ name = "Windows 11",
+ description = "",
+ url = "https://www.microsoft.com/en-us/software-download/windows11",
+ icon = null
+ ),
+ OsDownload(
+ name = "Hiren's BootCD PE",
+ description = "Hiren's BootCD PE (Preinstallation Environment) is a restored edition of Hiren's BootCD based on Windows PE",
+ url = "https://www.hirensbootcd.org/download/",
+ icon = null
+ ),
+ OsDownload(
+ name = "FreeBSD",
+ description = "FreeBSD is an operating system for a variety of platforms which focuses on features, speed, and stability.",
+ url = "https://www.freebsd.org/where/",
+ icon = "freebsd.svg"
+ ),
+ OsDownload(
+ name = "Linux Mint",
+ description = "Linux Mint is an operating system for desktop and laptop computers. It is designed to work 'out of the box' and comes fully equipped with the apps most people need.",
+ url = "https://linuxmint.com/download.php",
+ icon = "linuxmint.svg"
+ ),
+ OsDownload(
+ name = "Fedora",
+ description = "The Fedora Project is a community of people working together to build a free and open source software platform and to collaborate on and share user-focused solutions built on that platform.",
+ url = "https://www.fedoraproject.org/",
+ icon = "fedora.svg"
+ ),
+ OsDownload(
+ name = "Tails",
+ description = "Tails is a portable operating system that protects against surveillance and censorship.",
+ url = "https://tails.net/install/download/index.en.html",
+ icon = "tails.svg"
+ ),
+ OsDownload(
+ name = "Pop!_OS",
+ description = "Unleash your potential on a Linux operating system made to be productive and personal.",
+ url = "https://system76.com/pop/download/",
+ icon = "popos.svg"
+ )
+)
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun DownloadsScreen(
+ onNavigateBack: () -> Unit
+) {
+ val context = LocalContext.current
+
+ fun openUrl(url: String) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ context.startActivity(intent)
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Download ISOs") },
+ navigationIcon = {
+ IconButton(onClick = onNavigateBack) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back"
+ )
+ }
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
+ )
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(
+ text = "Operating Systems",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
+ )
+
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant
+ )
+ ) {
+ Column {
+ osDownloads.forEachIndexed { index, os ->
+ DownloadItem(
+ os = os,
+ onClick = { openUrl(os.url) }
+ )
+ if (index < osDownloads.lastIndex) {
+ HorizontalDivider(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
+ )
+ }
+ }
+ }
+ }
+
+ Text(
+ text = "Tap to open download page in browser",
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
+ modifier = Modifier.padding(16.dp)
+ )
+ }
+ }
+}
+
+@Composable
+private fun DownloadItem(
+ os: OsDownload,
+ onClick: () -> Unit
+) {
+ val context = LocalContext.current
+ val iconTint = MaterialTheme.colorScheme.onSurfaceVariant
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(onClick = onClick)
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (os.icon != null) {
+ AsyncImage(
+ model = ImageRequest.Builder(context)
+ .data("file:///android_asset/osicons/${os.icon}")
+ .decoderFactory(SvgDecoder.Factory())
+ .build(),
+ contentDescription = os.name,
+ modifier = Modifier.size(32.dp),
+ colorFilter = ColorFilter.tint(iconTint)
+ )
+ } else {
+ Icon(
+ imageVector = Icons.Default.Download,
+ contentDescription = null,
+ tint = iconTint,
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ Spacer(modifier = Modifier.width(16.dp))
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = os.name,
+ style = MaterialTheme.typography.titleMedium
+ )
+ if (os.description.isNotEmpty()) {
+ Text(
+ text = os.description,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ }
+ Icon(
+ imageVector = Icons.Default.OpenInNew,
+ contentDescription = "Open in browser",
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.size(20.dp)
+ )
+ }
+}
diff --git a/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt b/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt
index 254bb49..82b141c 100644
--- a/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt
+++ b/app/src/main/java/sh/sar/isodroid/ui/screens/MainScreen.kt
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Eject
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings
@@ -45,6 +46,7 @@ import sh.sar.isodroid.viewmodel.MainViewModel
@Composable
fun MainScreen(
viewModel: MainViewModel,
+ onNavigateToDownloads: () -> Unit,
onNavigateToSettings: () -> Unit
) {
val uiState by viewModel.uiState.collectAsState()
@@ -85,6 +87,12 @@ fun MainScreen(
contentDescription = "Refresh"
)
}
+ IconButton(onClick = onNavigateToDownloads) {
+ Icon(
+ imageVector = Icons.Default.Download,
+ contentDescription = "Download ISOs"
+ )
+ }
IconButton(onClick = onNavigateToSettings) {
Icon(
imageVector = Icons.Default.Settings,
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 99ed04b..d86a6ca 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -20,6 +20,9 @@ libsu = "6.0.0"
# DataStore
datastorePreferences = "1.1.1"
+# Coil
+coil = "2.6.0"
+
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -48,6 +51,10 @@ libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", versio
# DataStore
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
+# Coil
+coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
+coil-svg = { group = "io.coil-kt", name = "coil-svg", version.ref = "coil" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }