From 475ef04c16b097b4d56dbfc734b760a7ec4e4569 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Tue, 10 Mar 2026 01:36:28 +0500 Subject: [PATCH] add download page for common OS --- app/build.gradle.kts | 4 + app/src/main/assets/osicons/archlinux.svg | 1 + app/src/main/assets/osicons/debian.svg | 1 + app/src/main/assets/osicons/fedora.svg | 1 + app/src/main/assets/osicons/freebsd.svg | 1 + app/src/main/assets/osicons/linuxmint.svg | 1 + app/src/main/assets/osicons/nixos.svg | 1 + app/src/main/assets/osicons/opnsense.svg | 1 + app/src/main/assets/osicons/popos.svg | 1 + app/src/main/assets/osicons/tails.svg | 1 + app/src/main/assets/osicons/ubuntu.svg | 1 + .../main/java/sh/sar/isodroid/MainActivity.kt | 11 + .../isodroid/ui/screens/DownloadsScreen.kt | 256 ++++++++++++++++++ .../sh/sar/isodroid/ui/screens/MainScreen.kt | 8 + gradle/libs.versions.toml | 7 + 15 files changed, 296 insertions(+) create mode 100644 app/src/main/assets/osicons/archlinux.svg create mode 100644 app/src/main/assets/osicons/debian.svg create mode 100644 app/src/main/assets/osicons/fedora.svg create mode 100644 app/src/main/assets/osicons/freebsd.svg create mode 100644 app/src/main/assets/osicons/linuxmint.svg create mode 100644 app/src/main/assets/osicons/nixos.svg create mode 100644 app/src/main/assets/osicons/opnsense.svg create mode 100644 app/src/main/assets/osicons/popos.svg create mode 100644 app/src/main/assets/osicons/tails.svg create mode 100644 app/src/main/assets/osicons/ubuntu.svg create mode 100644 app/src/main/java/sh/sar/isodroid/ui/screens/DownloadsScreen.kt 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 @@ +Arch Linux \ 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 @@ +Debian \ 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 @@ +Fedora \ 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 @@ +FreeBSD \ 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 @@ +Linux Mint \ 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 @@ +NixOS \ 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 @@ +OPNsense \ 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 @@ +Pop!_OS \ 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 @@ +Tails \ 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 @@ +Ubuntu \ 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" }