Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd807ff304
|
||
|
|
0d5a73251c
|
||
|
|
9f09ac1ad0
|
||
|
|
6cdd812144
|
||
|
|
cbee1786ca
|
||
|
|
5ea665c02f
|
||
|
|
7c269d5e59
|
||
|
|
fbda9fedfb
|
||
|
|
79ccd6753e
|
||
|
|
3decb7307e
|
||
|
|
d297ab1059
|
||
|
|
371e38dc2f
|
||
|
|
1e850a2d1a
|
||
|
|
4cd9202609
|
||
|
|
6becf2907e
|
||
|
|
c34900a2b1
|
||
|
|
f654ba52a1
|
||
|
|
e7735a0446
|
||
|
|
1e0d5dd640
|
||
|
|
2672f8dfa5
|
||
|
|
157f5103f1
|
||
|
|
bcf0ffec4c
|
||
|
|
8a08b31f32
|
||
|
|
b974fd1b2b
|
||
|
|
881bb70e99
|
||
|
|
a0012b4d5e
|
@@ -0,0 +1,48 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.4] - 2026-03-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Directory browser for changing ISO directory in settings
|
||||||
|
- Create new directories from the directory browser
|
||||||
|
- Delete directories created by the app (long press)
|
||||||
|
- Shows ISO/IMG files with OS icons in directory browser
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Empty state on home screen now shows current path and helpful hints
|
||||||
|
- Version number is now read dynamically from app config
|
||||||
|
- Renamed "folder" to "directory" throughout the UI
|
||||||
|
|
||||||
|
## [1.3] - 2025-03-11
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Fix default image dir to be /sdcard/isodroid instead of /sdcard/isodrive
|
||||||
|
|
||||||
|
## [1.2] - 2025-03-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Fix app name displaying as "ISO Drive" instead of "ISO Droid" in some places
|
||||||
|
|
||||||
|
## [1.1] - 2025-03-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- New app icon
|
||||||
|
|
||||||
|
## [1.0] - 2025-03-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Mount ISO/IMG files as USB mass storage or CD-ROM
|
||||||
|
- Create blank IMG files for writable USB drives
|
||||||
|
- Read-only or read-write mount options
|
||||||
|
- Persistent notification with quick unmount button
|
||||||
|
- OS-specific icons for popular distributions (Linux, BSD, etc.)
|
||||||
|
- Download links to popular operating systems (Linux, BSD, Windows, Recovery tools)
|
||||||
|
- Rename and delete files
|
||||||
|
- Material 3 dynamic theming with dark mode support
|
||||||
|
- Support for Magisk, KernelSU, and APatch root solutions
|
||||||
|
- Bundled isodrive binary for all architectures (arm64-v8a, armeabi-v7a, x86_64, x86)
|
||||||
@@ -4,11 +4,9 @@ Android app for mounting ISO/IMG files as USB mass storage or CD-ROM devices on
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
| OS images listing | Mounted Status | Mount Options Dialog |
|
| OS images listing | Mounted Status | Mount Options Dialog | Create IMG Dialog | Download OS ISOs |
|
||||||
|:--:|:--:|:--:|
|
|:--:|:--:|:--:|:--:|:--:|
|
||||||
|  |  |  |
|
|  |  |  |  |  |
|
||||||
| **Create IMG Dialog** | **Download OS ISOs** | |
|
|
||||||
|  |  | |
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -37,7 +35,7 @@ Android app for mounting ISO/IMG files as USB mass storage or CD-ROM devices on
|
|||||||
1. Download the APK from the links above
|
1. Download the APK from the links above
|
||||||
2. Install the APK on your rooted Android device
|
2. Install the APK on your rooted Android device
|
||||||
3. Grant root access when prompted
|
3. Grant root access when prompted
|
||||||
4. Place your ISO/IMG files in `/sdcard/isodrive/` (or configure a different directory in settings)
|
4. Place your ISO/IMG files in `/sdcard/isodroid/` (or configure a different directory in settings)
|
||||||
|
|
||||||
> **Note**: The app includes a bundled `isodrive` binary. No additional setup required!
|
> **Note**: The app includes a bundled `isodrive` binary. No additional setup required!
|
||||||
|
|
||||||
@@ -65,7 +63,7 @@ Android app for mounting ISO/IMG files as USB mass storage or CD-ROM devices on
|
|||||||
|
|
||||||
- [isodrive](https://github.com/nitanmarcel/isodrive) by nitanmarcel - The CLI tool that powers ISO Droid
|
- [isodrive](https://github.com/nitanmarcel/isodrive) by nitanmarcel - The CLI tool that powers ISO Droid
|
||||||
- [ISODriveUT](https://github.com/fredldotme/ISODriveUT) by fredldotme - Original inspiration for isodrive
|
- [ISODriveUT](https://github.com/fredldotme/ISODriveUT) by fredldotme - Original inspiration for isodrive
|
||||||
- OS icons from [Simple Icons](https://simpleicons.org/)
|
- OS icons from [Simple Icons](https://simpleicons.org/) and [SVG Repo](https://www.svgrepo.com/)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "sh.sar.isodroid"
|
applicationId = "sh.sar.isodroid"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 4
|
||||||
versionName = "1.0"
|
versionName = "1.4"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,23 @@
|
|||||||
{
|
{
|
||||||
"name": "Arch Linux",
|
"name": "Arch Linux",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Enthusiast",
|
||||||
"description": "A simple, lightweight distribution",
|
"description": "A simple, lightweight distribution",
|
||||||
"icon": "archlinux.svg",
|
"icon": "archlinux.svg",
|
||||||
"url": "https://archlinux.org/download/"
|
"url": "https://archlinux.org/download/"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Gentoo Linux",
|
||||||
|
"category": "Linux",
|
||||||
|
"subcategory": "Enthusiast",
|
||||||
|
"description": "A highly flexible, source-based Linux distribution.",
|
||||||
|
"icon": "gentoo.svg",
|
||||||
|
"url": "https://www.gentoo.org/downloads/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Debian",
|
"name": "Debian",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Server",
|
||||||
"description": "Debian is a complete Free Operating System!",
|
"description": "Debian is a complete Free Operating System!",
|
||||||
"icon": "debian.svg",
|
"icon": "debian.svg",
|
||||||
"url": "https://www.debian.org/download"
|
"url": "https://www.debian.org/download"
|
||||||
@@ -16,6 +26,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Fedora",
|
"name": "Fedora",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Desktop",
|
||||||
"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.",
|
"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.",
|
||||||
"icon": "fedora.svg",
|
"icon": "fedora.svg",
|
||||||
"url": "https://www.fedoraproject.org/"
|
"url": "https://www.fedoraproject.org/"
|
||||||
@@ -23,6 +34,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Linux Mint",
|
"name": "Linux Mint",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Desktop",
|
||||||
"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.",
|
"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.",
|
||||||
"icon": "linuxmint.svg",
|
"icon": "linuxmint.svg",
|
||||||
"url": "https://linuxmint.com/download.php"
|
"url": "https://linuxmint.com/download.php"
|
||||||
@@ -30,6 +42,7 @@
|
|||||||
{
|
{
|
||||||
"name": "NixOS",
|
"name": "NixOS",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Enthusiast",
|
||||||
"description": "Declarative builds and deployments.",
|
"description": "Declarative builds and deployments.",
|
||||||
"icon": "nixos.svg",
|
"icon": "nixos.svg",
|
||||||
"url": "https://nixos.org/download/#nixos-iso"
|
"url": "https://nixos.org/download/#nixos-iso"
|
||||||
@@ -37,6 +50,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Pop!_OS",
|
"name": "Pop!_OS",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Desktop",
|
||||||
"description": "Unleash your potential on a Linux operating system made to be productive and personal.",
|
"description": "Unleash your potential on a Linux operating system made to be productive and personal.",
|
||||||
"icon": "popos.svg",
|
"icon": "popos.svg",
|
||||||
"url": "https://system76.com/pop/download/"
|
"url": "https://system76.com/pop/download/"
|
||||||
@@ -44,6 +58,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Tails",
|
"name": "Tails",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Security",
|
||||||
"description": "Tails is a portable operating system that protects against surveillance and censorship.",
|
"description": "Tails is a portable operating system that protects against surveillance and censorship.",
|
||||||
"icon": "tails.svg",
|
"icon": "tails.svg",
|
||||||
"url": "https://tails.net/install/download/index.en.html"
|
"url": "https://tails.net/install/download/index.en.html"
|
||||||
@@ -51,6 +66,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Ubuntu",
|
"name": "Ubuntu",
|
||||||
"category": "Linux",
|
"category": "Linux",
|
||||||
|
"subcategory": "Desktop",
|
||||||
"description": "",
|
"description": "",
|
||||||
"icon": "ubuntu.svg",
|
"icon": "ubuntu.svg",
|
||||||
"url": "https://ubuntu.com/download"
|
"url": "https://ubuntu.com/download"
|
||||||
@@ -58,6 +74,7 @@
|
|||||||
{
|
{
|
||||||
"name": "FreeBSD",
|
"name": "FreeBSD",
|
||||||
"category": "BSD",
|
"category": "BSD",
|
||||||
|
"subcategory": null,
|
||||||
"description": "FreeBSD is an operating system for a variety of platforms which focuses on features, speed, and stability.",
|
"description": "FreeBSD is an operating system for a variety of platforms which focuses on features, speed, and stability.",
|
||||||
"icon": "freebsd.svg",
|
"icon": "freebsd.svg",
|
||||||
"url": "https://www.freebsd.org/where/"
|
"url": "https://www.freebsd.org/where/"
|
||||||
@@ -65,22 +82,121 @@
|
|||||||
{
|
{
|
||||||
"name": "OPNsense",
|
"name": "OPNsense",
|
||||||
"category": "BSD",
|
"category": "BSD",
|
||||||
|
"subcategory": "Networking",
|
||||||
"description": "OPNsense® is an open source, feature rich firewall and routing platform, offering cutting-edge network protection.",
|
"description": "OPNsense® is an open source, feature rich firewall and routing platform, offering cutting-edge network protection.",
|
||||||
"icon": "opnsense.svg",
|
"icon": "opnsense.svg",
|
||||||
"url": "https://opnsense.org/download/"
|
"url": "https://opnsense.org/download/"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "pfSense",
|
||||||
|
"category": "BSD",
|
||||||
|
"subcategory": "Networking",
|
||||||
|
"description": "Secure networks start here.™",
|
||||||
|
"icon": "pfsense.svg",
|
||||||
|
"url": "https://www.pfsense.org/download/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Windows 11",
|
"name": "Windows 11",
|
||||||
"category": "Windows",
|
"category": "Windows",
|
||||||
|
"subcategory": null,
|
||||||
"description": "",
|
"description": "",
|
||||||
"icon": "windows11.svg",
|
"icon": "windows11.svg",
|
||||||
"url": "https://www.microsoft.com/en-us/software-download/windows11"
|
"url": "https://www.microsoft.com/en-us/software-download/windows11"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Windows 10",
|
||||||
|
"category": "Windows",
|
||||||
|
"subcategory": null,
|
||||||
|
"description": "",
|
||||||
|
"icon": "windows10.svg",
|
||||||
|
"url": "https://www.microsoft.com/en-us/software-download/windows10ISO"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Hiren's BootCD PE",
|
"name": "Hiren's BootCD PE",
|
||||||
"category": "Recovery",
|
"category": "Recovery",
|
||||||
|
"subcategory": null,
|
||||||
"description": "Hiren's BootCD PE (Preinstallation Environment) is a restored edition of Hiren's BootCD based on Windows PE",
|
"description": "Hiren's BootCD PE (Preinstallation Environment) is a restored edition of Hiren's BootCD based on Windows PE",
|
||||||
"icon": null,
|
"icon": null,
|
||||||
"url": "https://www.hirensbootcd.org/download/"
|
"url": "https://www.hirensbootcd.org/download/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Clonezilla",
|
||||||
|
"category": "Recovery",
|
||||||
|
"subcategory": null,
|
||||||
|
"description": "Clonezilla is a partition and disk imaging/cloning program.",
|
||||||
|
"icon": null,
|
||||||
|
"url": "https://clonezilla.org/downloads.php"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bazzite",
|
||||||
|
"category": "Linux",
|
||||||
|
"subcategory": "Desktop",
|
||||||
|
"description": "Bazzite makes gaming and everyday use smoother and simpler across desktop PCs, handhelds, tablets, and home theater PCs.",
|
||||||
|
"icon": null,
|
||||||
|
"url": "https://bazzite.gg/#image-picker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Manjaro",
|
||||||
|
"category": "Linux",
|
||||||
|
"subcategory": "Desktop",
|
||||||
|
"description": "Taking the raw power and flexibility of Arch Linux and making it more accessible for a greater audience.",
|
||||||
|
"icon": "manjaro.svg",
|
||||||
|
"url": "https://manjaro.org/products"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Super Grub2",
|
||||||
|
"category": "Recovery",
|
||||||
|
"subcategory": null,
|
||||||
|
"description": "Get back to your GNU/Linux & Windows computers !",
|
||||||
|
"icon": null,
|
||||||
|
"url": "https://www.supergrubdisk.org/category/download/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kali Linux",
|
||||||
|
"category": "Linux",
|
||||||
|
"subcategory": "Security",
|
||||||
|
"description": "The most advanced penetration testing platform ever made.",
|
||||||
|
"icon": "kalilinux.svg",
|
||||||
|
"url": "https://www.kali.org/get-kali/#kali-installer-images"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ParrotOS",
|
||||||
|
"category": "Linux",
|
||||||
|
"subcategory": "Security",
|
||||||
|
"description": "The ultimate framework for your Cyber Security operations",
|
||||||
|
"icon": "parrotsecurity.svg",
|
||||||
|
"url": "https://www.parrotsec.org/download/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ProxmoxVE",
|
||||||
|
"category": "Linux",
|
||||||
|
"subcategory": "Server",
|
||||||
|
"description": "Proxmox Virtual Environment is a complete open-source platform for enterprise virtualization.",
|
||||||
|
"icon": "proxmox.svg",
|
||||||
|
"url": "https://www.proxmox.com/en/downloads/proxmox-virtual-environment/iso"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Android-x86",
|
||||||
|
"category": "Android",
|
||||||
|
"subcategory": null,
|
||||||
|
"description": "This is a project to port Android Open Source Project to x86 platform",
|
||||||
|
"icon": "android.svg",
|
||||||
|
"url": "https://www.android-x86.org/download"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RemixOS",
|
||||||
|
"category": "Android",
|
||||||
|
"subcategory": null,
|
||||||
|
"description": "RemixOS brings Android to laptops and PCs with a powerful desktop-like experience.",
|
||||||
|
"icon": null,
|
||||||
|
"url": "https://www.fosshub.com/Remix-OS.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TempleOS",
|
||||||
|
"category": "Other",
|
||||||
|
"subcategory": null,
|
||||||
|
"description": "",
|
||||||
|
"icon": null,
|
||||||
|
"url": "https://templeos.org/Downloads/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Android</title><path d="M18.4395 5.5586c-.675 1.1664-1.352 2.3318-2.0274 3.498-.0366-.0155-.0742-.0286-.1113-.043-1.8249-.6957-3.484-.8-4.42-.787-1.8551.0185-3.3544.4643-4.2597.8203-.084-.1494-1.7526-3.021-2.0215-3.4864a1.1451 1.1451 0 0 0-.1406-.1914c-.3312-.364-.9054-.4859-1.379-.203-.475.282-.7136.9361-.3886 1.5019 1.9466 3.3696-.0966-.2158 1.9473 3.3593.0172.031-.4946.2642-1.3926 1.0177C2.8987 12.176.452 14.772 0 18.9902h24c-.119-1.1108-.3686-2.099-.7461-3.0683-.7438-1.9118-1.8435-3.2928-2.7402-4.1836a12.1048 12.1048 0 0 0-2.1309-1.6875c.6594-1.122 1.312-2.2559 1.9649-3.3848.2077-.3615.1886-.7956-.0079-1.1191a1.1001 1.1001 0 0 0-.8515-.5332c-.5225-.0536-.9392.3128-1.0488.5449zm-.0391 8.461c.3944.5926.324 1.3306-.1563 1.6503-.4799.3197-1.188.0985-1.582-.4941-.3944-.5927-.324-1.3307.1563-1.6504.4727-.315 1.1812-.1086 1.582.4941zM7.207 13.5273c.4803.3197.5506 1.0577.1563 1.6504-.394.5926-1.1038.8138-1.584.4941-.48-.3197-.5503-1.0577-.1563-1.6504.4008-.6021 1.1087-.8106 1.584-.4941z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>elementary</title><path d="M12 0a12 12 0 1 0 0 24 12 12 0 0 0 0-24zm0 1a11 11 0 0 1 10.59 8.01 19.09 19.09 0 0 1-4.66 6.08c-.94.81-1.96 1.53-3.08 2.04-1.13.5-2.37.8-3.6.72a6.23 6.23 0 0 1-2.66-.76 20.02 20.02 0 0 0 5.68-4.58 9.97 9.97 0 0 0 2.31-4.17c.18-.79.2-1.6.04-2.4a4.42 4.42 0 0 0-1.08-2.11 4.33 4.33 0 0 0-2-1.19 5.25 5.25 0 0 0-2.33-.08A7.8 7.8 0 0 0 7.2 4.85a9.77 9.77 0 0 0-2.94 7.49 7.88 7.88 0 0 0 1.95 4.59 18 18 0 0 1-3.56.85A11 11 0 0 1 12 1zm.07 2.22c.77 0 1.55.24 2.17.7.55.42.97 1.02 1.2 1.68.23.65.3 1.37.21 2.06a7.85 7.85 0 0 1-1.7 3.76 16.22 16.22 0 0 1-6.37 4.96c-.48-.42-.9-.92-1.2-1.48a6.61 6.61 0 0 1-.75-3.87c.12-1.32.58-2.6 1.2-3.79a7.92 7.92 0 0 1 3.02-3.42c.68-.37 1.45-.6 2.22-.6zm10.83 7.3A11 11 0 0 1 3.52 19a19.8 19.8 0 0 0 3.63-1.2c.51.4 1.08.71 1.67.94a8 8 0 0 0 5.44-.04 13.3 13.3 0 0 0 4.64-2.95 20 20 0 0 0 4-5.22z"/></svg>
|
||||||
|
After Width: | Height: | Size: 940 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Gentoo</title><path d="M9.94 0a7.31 7.31 0 00-1.26.116c-4.344.795-7.4 4.555-7.661 7.031-.126 1.215.53 2.125.89 2.526.977 1.085 2.924 1.914 4.175 2.601-1.81 1.543-2.64 2.296-3.457 3.154C1.403 16.712.543 18.125.54 19.138c0 .325-.053 1.365.371 2.187.16.309.613 1.338 1.98 2.109.874.494 2.119.675 3.337.501 3.772-.538 8.823-3.737 12.427-6.716 2.297-1.9 3.977-3.739 4.462-4.644.39-.731.434-2.043.207-2.866-.645-2.337-5.887-7.125-10.172-9.051A7.824 7.824 0 009.94 0zm-.008.068a7.4 7.4 0 013.344.755c3.46 1.7 9.308 6.482 9.739 8.886.534 2.972-9.931 11.017-16.297 12.272-2.47.485-4.576.618-5.537-1.99-.832-2.262.783-3.916 3.16-6.09a92.546 92.546 0 012.96-2.576c.065-.069-5.706-2.059-5.89-4.343C1.221 4.634 4.938.3 9.697.076c.08-.004.157-.007.235-.008zm-.112.52a5.647 5.647 0 00-.506.032c-2.337.245-2.785.547-4.903 2.149-.71.537-2.016 1.844-2.35 3.393-.128.59.024 1.1.448 1.458 1.36 1.144 3.639 2.072 5.509 2.97.547.263.185.74-.698 1.505-2.227 1.928-5.24 4.276-5.45 6.066-.099.842.19 1.988 1.213 2.574 1.195.685 3.676.238 5.333-.379 2.422-.902 5.602-2.892 8.127-4.848 2.625-2.034 5.067-4.617 5.188-5.038.148-.517.133-.996-.154-1.546-.448-.862-1.049-1.503-1.694-2.22-1.732-1.825-3.563-3.43-5.754-4.658C12.694 1.242 11.417.564 9.82.588zm1.075 3.623c.546 0 1.176.173 1.853.5 1.688.817 3.422 2.961-.015 4.195-.935.336-3.9-.824-3.81-2.407.09-1.57.854-2.289 1.972-2.288zm.285 1.367c-.317-.002-.575.079-.694.263-.557.861-.303 1.472.212 1.862.192-.457 2.156.043 2.148.472a.32.32 0 00.055-.032c1.704-1.282-.472-2.557-1.72-2.565z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Manjaro</title><path d="M2.182 0A2.177 2.177 0 0 0 0 2.182v19.636C0 23.027.973 24 2.182 24h4.363V6.545h8.728V0Zm15.273 0v24h4.363A2.177 2.177 0 0 0 24 21.818V2.182A2.177 2.177 0 0 0 21.818 0ZM8.727 8.727V24h6.546V8.727Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>openSUSE</title><path d="M10.724 0a12 12 0 0 0-9.448 4.623c1.464.391 2.5.727 2.81.832.005-.19.037-1.893.037-1.893s.004-.04.025-.06c.026-.026.065-.018.065-.018.385.056 8.602 1.274 12.066 3.292.427.25.638.517.902.786.958.99 2.223 5.108 2.359 5.957.005.033-.036.07-.054.083a5.177 5.177 0 0 1-.313.228c-.82.55-2.708 1.872-5.13 1.656-2.176-.193-5.018-1.44-8.445-3.699.336.79.668 1.58 1 2.371.497.258 5.287 2.7 7.651 2.651 1.904-.04 3.941-.968 4.756-1.458 0 0 .179-.108.257-.048.085.066.061.167.041.27-.05.234-.164.66-.242.863l-.065.165c-.093.25-.183.482-.356.625-.48.436-1.246.784-2.446 1.305-1.855.812-4.865 1.328-7.66 1.31-1.001-.022-1.968-.133-2.817-.232-1.743-.197-3.161-.357-4.026.269A12 12 0 0 0 10.724 24a12 12 0 0 0 12-12 12 12 0 0 0-12-12zM13.4 6.963a3.503 3.503 0 0 0-2.521.942 3.498 3.498 0 0 0-1.114 2.449 3.528 3.528 0 0 0 3.39 3.64 3.48 3.48 0 0 0 2.524-.946 3.504 3.504 0 0 0 1.114-2.446 3.527 3.527 0 0 0-3.393-3.64zm-.03 1.035a2.458 2.458 0 0 1 2.368 2.539 2.43 2.43 0 0 1-.774 1.706 2.456 2.456 0 0 1-1.762.659 2.461 2.461 0 0 1-2.364-2.542c.02-.655.3-1.26.777-1.707a2.419 2.419 0 0 1 1.756-.655zm.402 1.23c-.602 0-1.087.325-1.087.727 0 .4.485.725 1.087.725.6 0 1.088-.326 1.088-.725 0-.402-.487-.726-1.088-.726Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
./parrotsecurity.svg
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
./parrotsecurity.svg
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Parrot Security</title><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0Zm6.267 2.784L13.03 5.54l8.05-.179-8.05 3.333-2.154 2.688 5.007 9.038-1.536-1.605 1.645 3.456-4.937-5.527-6.268-6.28L2.77 12.11l.7-3.442 4.018-.261.823-4.06Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 351 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>pfSense</title><path d="M2.013 0C.898 0 0 .929 0 2.044v17.775L3.252 8.27h3.282L6.1 9.785h.063c.186-.217.433-.403.742-.62.31-.216.62-.402.96-.588.342-.186.713-.31 1.116-.433.402-.124.805-.155 1.208-.155.867 0 1.579.154 2.198.433.62.279 1.084.712 1.455 1.239.31.464.5 1.019.593 1.669.006.06.027.135.027.189.062.712-.031 1.518-.28 2.385a8.571 8.571 0 0 1-1.02 2.322 9.885 9.885 0 0 1-1.58 1.95 8.125 8.125 0 0 1-2.044 1.364 5.536 5.536 0 0 1-2.354.495 5.655 5.655 0 0 1-1.982-.34c-.588-.217-.99-.62-1.238-1.177h-.062L2.353 24h19.603A2.042 2.042 0 0 0 24 21.956V4.706c-.093-.03-.186-.06-.248-.092a2.771 2.771 0 0 0-.557-.062c-.557 0-1.022.124-1.394.372-.34.248-.65.743-.867 1.518l-.526 1.826h2.013l.495 1.58-1.3 1.27h-2.014l-2.446 8.67h-3.53l2.446-8.67h-1.455l.805-2.85h1.425l.557-2.044c.185-.619.403-1.238.681-1.795a4.996 4.996 0 0 1 1.053-1.487c.433-.434.99-.775 1.641-1.022.65-.248 1.487-.372 2.447-.372.248 0 .464 0 .712.031A2.082 2.082 0 0 0 21.988 0zm6.565 11.118c-.898 0-1.672.278-2.323.805-.65.526-1.083 1.239-1.331 2.106-.248.867-.217 1.579.155 2.105.31.557.929.805 1.858.805.898 0 1.672-.278 2.322-.805.65-.526 1.115-1.238 1.363-2.105.247-.867.185-1.58-.155-2.106-.34-.527-.991-.805-1.89-.805Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
proxmox.svg
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Slackware</title><path d="M12.924 1.228c-.584-.01-1.251 0-1.485.027-2.46.282-4.138 1.3-4.753 2.891-.218.552-.274 1.002-.243 1.772.048 1.21.419 2.004 1.262 2.742 1.225 1.06 2.98 1.508 5.998 1.508 2.737 0 3.71.413 3.916 1.675.313 1.867-1.57 3.07-4.414 2.827-1.878-.16-3.496-.912-4.223-1.967a7.772 7.772 0 01-.355-.62c-.382-.76-.64-.978-1.176-.978-.43.005-.732.165-.918.494l-.133.24v4.03l.137.296c.165.344.4.546.744.63.35.09.794-.036 1.42-.402l.5-.29.826.185c1.82.403 2.75.523 4.065.523 1.103.005 1.548-.046 2.455-.285 1.124-.297 1.974-.785 2.717-1.57.8-.844 1.15-1.853 1.097-3.147-.069-1.628-.695-2.698-2-3.414-.96-.525-2.292-.79-4.377-.88-2.042-.086-2.794-.155-3.515-.32-.51-.12-.785-.25-1.076-.515-.653-.589-.59-1.755.136-2.482.642-.637 1.511-.928 2.774-.928 1.432.005 2.393.27 3.412.955.185.127.721.62 1.193 1.092.886.902 1.135 1.082 1.506 1.082.244 0 .59-.163.732-.344.26-.329.303-.63.303-2.2 0-1.66-.043-1.91-.377-2.282-.387-.425-.848-.42-1.75.031l-.59.297-.63-.17c-1.496-.392-2.038-.477-3.178-.504zM0 13.775v9h24v-1H1v-8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Solus</title><path d="M7.453 0c-.18.587-.369 1.167-.565 1.75A11.638 11.638 0 0 0 0 12.364a11.638 11.638 0 0 0 .516 3.403l-.339.598L0 16.73l.279.143a3.448 3.448 0 0 0 .741.222A11.638 11.638 0 0 0 2 18.868c4.034.343 8.55.512 12.446-.056 3.192-.463 5.94-1.423 7.735-3.117.252-.233.474-.474.674-.722.019-.038.037-.053.06-.076.011 0 .026-.037.038-.052.015 0 .03-.038.041-.057.008 0 .015-.038.023-.038.33-.444.587-.892.801-1.31l.181-.365-.365-.365a5.936 5.936 0 0 0-.361-.35A11.638 11.638 0 0 0 11.635.722a11.638 11.638 0 0 0-3.211.463C7.96.508 7.596.041 7.453 0zm.365 1.637C9.06 3.82 10.13 5.06 11.454 7.457c.132 1.524.67 9.45.727 10.181-.392-.037-2.485-.24-5.104-.515-1.43-.147-2.899-.316-4.092-.49l-1.9-.447c2.149-3.787 5.551-9.727 6.737-14.548zm4.543 6.18s4.991 3.927 7.092 8.73c-2.56 1.26-4.916 1.098-6.361 1.09 1.023-2.634 1.023-6.21-.73-9.82zm3.456 2.184a45.14 45.14 0 0 1 2.91.907c1.768.629 3.417 1.49 4.365 2.364a6.956 6.956 0 0 1-2.91 2.91c.151-1.495-.39-2.933-1.456-4.002-.787-.787-1.822-1.453-2.91-2.183zm6.707 6.478c-2.352 1.667-5.126 2.68-7.965 3.112a41.026 41.026 0 0 1-3.715.34h-.323a53.48 53.48 0 0 1-3.727 0 85.763 85.763 0 0 1-4.178-.23h-.003c2.555 3.255 6.993 4.893 11.092 4.102a11.367 11.367 0 0 0 4.498-1.852 11.638 11.638 0 0 0 .007 0c.312-.214.614-.444.903-.685a11.638 11.638 0 0 0 .038-.037 11.555 11.555 0 0 0 3.376-4.762zM2.511 19.584a11.638 11.638 0 0 0 .023.038c-.008 0-.015-.038-.023-.038z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Zorin</title><path d="M4 18.944L5.995 22.4h12.01L20 18.944H4zM24 12l-2.013 3.488H9.216l12.771-6.976L24 12zM0 12l2.013-3.488h12.771L2.013 15.488 0 12zm4-6.944L5.995 1.6h12.01L20 5.056H4z"/></svg>
|
||||||
|
After Width: | Height: | Size: 272 B |
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid
|
package sh.sar.isodroid
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
@@ -12,7 +17,7 @@ class ISODroidApp : Application() {
|
|||||||
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||||
Shell.setDefaultBuilder(
|
Shell.setDefaultBuilder(
|
||||||
Shell.Builder.create()
|
Shell.Builder.create()
|
||||||
.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_REDIRECT_STDERR)
|
.setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||||
.setTimeout(10)
|
.setTimeout(10)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -23,7 +28,7 @@ class ISODroidApp : Application() {
|
|||||||
Shell.enableVerboseLogging = true
|
Shell.enableVerboseLogging = true
|
||||||
Shell.setDefaultBuilder(
|
Shell.setDefaultBuilder(
|
||||||
Shell.Builder.create()
|
Shell.Builder.create()
|
||||||
.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_REDIRECT_STDERR)
|
.setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||||
.setTimeout(10)
|
.setTimeout(10)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid
|
package sh.sar.isodroid
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.data
|
package sh.sar.isodroid.data
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.data
|
package sh.sar.isodroid.data
|
||||||
|
|
||||||
data class MountOptions(
|
data class MountOptions(
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.data
|
package sh.sar.isodroid.data
|
||||||
|
|
||||||
enum class MountType {
|
enum class MountType {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.isodrive
|
package sh.sar.isodroid.isodrive
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.isodrive
|
package sh.sar.isodroid.isodrive
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.isodrive
|
package sh.sar.isodroid.isodrive
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.notification
|
package sh.sar.isodroid.notification
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.notification
|
package sh.sar.isodroid.notification
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.notification
|
package sh.sar.isodroid.notification
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.root
|
package sh.sar.isodroid.root
|
||||||
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.components
|
package sh.sar.isodroid.ui.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -71,9 +76,10 @@ fun CreateImgDialog(
|
|||||||
|
|
||||||
val fullFileName = if (fileName.isNotBlank()) "$fileName.img" else ""
|
val fullFileName = if (fileName.isNotBlank()) "$fileName.img" else ""
|
||||||
val fileExists = fullFileName.isNotEmpty() && existingFiles.contains(fullFileName)
|
val fileExists = fullFileName.isNotEmpty() && existingFiles.contains(fullFileName)
|
||||||
|
val hasInvalidChars = fileName.any { !it.isLetterOrDigit() && it !in "-_. ()[]+," }
|
||||||
|
|
||||||
val isValidInput = fileName.isNotBlank() &&
|
val isValidInput = fileName.isNotBlank() &&
|
||||||
!fileName.contains("/") &&
|
!hasInvalidChars &&
|
||||||
sizeValue.toLongOrNull()?.let { it > 0 } == true &&
|
sizeValue.toLongOrNull()?.let { it > 0 } == true &&
|
||||||
!fileExists
|
!fileExists
|
||||||
|
|
||||||
@@ -130,11 +136,11 @@ fun CreateImgDialog(
|
|||||||
// File name input
|
// File name input
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = fileName,
|
value = fileName,
|
||||||
onValueChange = { fileName = it.replace("/", "") },
|
onValueChange = { fileName = it },
|
||||||
label = { Text("File Name") },
|
label = { Text("File Name") },
|
||||||
suffix = { Text(".img") },
|
suffix = { Text(".img") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = fileExists,
|
isError = fileExists || hasInvalidChars,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,6 +154,16 @@ fun CreateImgDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show error if invalid characters
|
||||||
|
if (hasInvalidChars) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Invalid file name",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
// Size input
|
// Size input
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.components
|
package sh.sar.isodroid.ui.components
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
@@ -50,7 +55,7 @@ import sh.sar.isodroid.data.IsoFile
|
|||||||
* 1. Longest match (most specific)
|
* 1. Longest match (most specific)
|
||||||
* 2. Earliest position in filename (if same length)
|
* 2. Earliest position in filename (if same length)
|
||||||
*/
|
*/
|
||||||
private fun findOsIcon(context: android.content.Context, filename: String): String? {
|
fun findOsIcon(context: android.content.Context, filename: String): String? {
|
||||||
return try {
|
return try {
|
||||||
// Dynamically load available icon files from assets
|
// Dynamically load available icon files from assets
|
||||||
val availableIcons = context.assets.list("osicons")
|
val availableIcons = context.assets.list("osicons")
|
||||||
@@ -106,10 +111,19 @@ fun FileBrowser(
|
|||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Place ISO or IMG files in this directory",
|
text = "Place ISO or IMG files in:\n$currentPath",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||||
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = "Tap + to create an empty IMG file\nChange directory in Settings",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||||
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.components
|
package sh.sar.isodroid.ui.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -165,7 +170,8 @@ private fun RenameDialog(
|
|||||||
val nameWithoutExtension = currentName.removeSuffix(extension)
|
val nameWithoutExtension = currentName.removeSuffix(extension)
|
||||||
|
|
||||||
var newName by remember { mutableStateOf(nameWithoutExtension) }
|
var newName by remember { mutableStateOf(nameWithoutExtension) }
|
||||||
val isValid = newName.isNotBlank() && !newName.contains("/")
|
val hasInvalidChars = newName.any { !it.isLetterOrDigit() && it !in "-_. ()[]+," }
|
||||||
|
val isValid = newName.isNotBlank() && !hasInvalidChars
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
@@ -174,12 +180,22 @@ private fun RenameDialog(
|
|||||||
Column {
|
Column {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = newName,
|
value = newName,
|
||||||
onValueChange = { newName = it.replace("/", "") },
|
onValueChange = { newName = it },
|
||||||
label = { Text("File Name") },
|
label = { Text("File Name") },
|
||||||
suffix = { Text(extension) },
|
suffix = { Text(extension) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
isError = hasInvalidChars,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (hasInvalidChars) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Invalid file name",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.components
|
package sh.sar.isodroid.ui.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.components
|
package sh.sar.isodroid.ui.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -42,30 +47,6 @@ import sh.sar.isodroid.ui.theme.MountedGreen
|
|||||||
import sh.sar.isodroid.ui.theme.UnmountedGray
|
import sh.sar.isodroid.ui.theme.UnmountedGray
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a matching OS icon filename for a given file by dynamically checking available icons.
|
|
||||||
*/
|
|
||||||
private fun findOsIcon(context: android.content.Context, filename: String): String? {
|
|
||||||
return try {
|
|
||||||
val availableIcons = context.assets.list("osicons")
|
|
||||||
?.filter { it.endsWith(".svg", ignoreCase = true) }
|
|
||||||
?.map { it.removeSuffix(".svg").lowercase() }
|
|
||||||
?: emptyList()
|
|
||||||
|
|
||||||
val lowerFilename = filename.lowercase()
|
|
||||||
|
|
||||||
availableIcons
|
|
||||||
.filter { lowerFilename.contains(it) }
|
|
||||||
.maxWithOrNull(compareBy(
|
|
||||||
{ it.length },
|
|
||||||
{ -lowerFilename.indexOf(it) }
|
|
||||||
))
|
|
||||||
?.let { "$it.svg" }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StatusCard(
|
fun StatusCard(
|
||||||
mountStatus: MountStatus,
|
mountStatus: MountStatus,
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.screens
|
package sh.sar.isodroid.ui.screens
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -45,6 +50,7 @@ import org.json.JSONArray
|
|||||||
data class OsDownload(
|
data class OsDownload(
|
||||||
val name: String,
|
val name: String,
|
||||||
val category: String,
|
val category: String,
|
||||||
|
val subcategory: String?,
|
||||||
val description: String,
|
val description: String,
|
||||||
val icon: String?,
|
val icon: String?,
|
||||||
val url: String
|
val url: String
|
||||||
@@ -62,6 +68,7 @@ private fun loadOsDownloads(context: Context): List<OsDownload> {
|
|||||||
OsDownload(
|
OsDownload(
|
||||||
name = obj.getString("name"),
|
name = obj.getString("name"),
|
||||||
category = obj.getString("category"),
|
category = obj.getString("category"),
|
||||||
|
subcategory = if (obj.isNull("subcategory")) null else obj.optString("subcategory", null),
|
||||||
description = obj.optString("description", ""),
|
description = obj.optString("description", ""),
|
||||||
icon = if (obj.isNull("icon")) null else obj.optString("icon", null),
|
icon = if (obj.isNull("icon")) null else obj.optString("icon", null),
|
||||||
url = obj.getString("url")
|
url = obj.getString("url")
|
||||||
@@ -83,11 +90,9 @@ fun DownloadsScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val downloads = remember { loadOsDownloads(context) }
|
val downloads = remember { loadOsDownloads(context) }
|
||||||
|
|
||||||
// Group by category and maintain order: Linux, BSD, Windows, Recovery
|
// Group by category alphabetically
|
||||||
val categoryOrder = listOf("Linux", "BSD", "Windows", "Recovery")
|
|
||||||
val groupedDownloads = remember(downloads) {
|
val groupedDownloads = remember(downloads) {
|
||||||
downloads.groupBy { it.category }
|
downloads.groupBy { it.category }.toSortedMap()
|
||||||
.toSortedMap(compareBy { categoryOrder.indexOf(it).takeIf { i -> i >= 0 } ?: Int.MAX_VALUE })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openUrl(url: String) {
|
fun openUrl(url: String) {
|
||||||
@@ -161,22 +166,86 @@ private fun DownloadCategory(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
downloads.forEachIndexed { index, os ->
|
// Check if any items have subcategories
|
||||||
DownloadItem(
|
val hasSubcategories = downloads.any { it.subcategory != null }
|
||||||
os = os,
|
|
||||||
onClick = { onItemClick(os) }
|
if (hasSubcategories) {
|
||||||
)
|
// Group by subcategory alphabetically
|
||||||
if (index < downloads.lastIndex) {
|
val groupedBySubcategory = downloads
|
||||||
HorizontalDivider(
|
.groupBy { it.subcategory ?: "" }
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
.toSortedMap()
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
|
||||||
|
var isFirstGroup = true
|
||||||
|
groupedBySubcategory.forEach { (subcategory, osList) ->
|
||||||
|
if (subcategory.isNotEmpty()) {
|
||||||
|
// Subcategory header
|
||||||
|
if (!isFirstGroup) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SubcategoryHeader(subcategory)
|
||||||
|
}
|
||||||
|
isFirstGroup = false
|
||||||
|
|
||||||
|
osList.sortedBy { it.name.lowercase() }.forEachIndexed { index, os ->
|
||||||
|
DownloadItem(
|
||||||
|
os = os,
|
||||||
|
onClick = { onItemClick(os) }
|
||||||
|
)
|
||||||
|
if (index < osList.lastIndex) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.15f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No subcategories, show flat list
|
||||||
|
downloads.forEachIndexed { index, os ->
|
||||||
|
DownloadItem(
|
||||||
|
os = os,
|
||||||
|
onClick = { onItemClick(os) }
|
||||||
)
|
)
|
||||||
|
if (index < downloads.lastIndex) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SubcategoryHeader(title: String) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp)
|
||||||
|
)
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DownloadItem(
|
private fun DownloadItem(
|
||||||
os: OsDownload,
|
os: OsDownload,
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.screens
|
package sh.sar.isodroid.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -84,7 +89,7 @@ fun MainScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("ISO Drive") },
|
title = { Text("ISO Droid") },
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.screens
|
package sh.sar.isodroid.ui.screens
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
@@ -5,23 +10,38 @@ import android.content.Intent
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||||
|
import androidx.compose.material.icons.filled.Album
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.filled.Code
|
import androidx.compose.material.icons.filled.Code
|
||||||
|
import androidx.compose.material.icons.filled.CreateNewFolder
|
||||||
import androidx.compose.material.icons.filled.Error
|
import androidx.compose.material.icons.filled.Error
|
||||||
import androidx.compose.material.icons.filled.Folder
|
import androidx.compose.material.icons.filled.Folder
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
@@ -30,6 +50,7 @@ import androidx.compose.material.icons.filled.Security
|
|||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -42,20 +63,33 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.decode.SvgDecoder
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import sh.sar.isodroid.root.RootManager
|
||||||
|
import sh.sar.isodroid.ui.components.findOsIcon
|
||||||
import sh.sar.isodroid.viewmodel.MainViewModel
|
import sh.sar.isodroid.viewmodel.MainViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -68,7 +102,6 @@ fun SettingsScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val activity = context as? androidx.activity.ComponentActivity
|
val activity = context as? androidx.activity.ComponentActivity
|
||||||
var showPathDialog by remember { mutableStateOf(false) }
|
var showPathDialog by remember { mutableStateOf(false) }
|
||||||
var tempPath by remember(uiState.currentPath) { mutableStateOf(uiState.isoDirectory) }
|
|
||||||
|
|
||||||
// Track notification permission with lifecycle-aware refresh
|
// Track notification permission with lifecycle-aware refresh
|
||||||
var hasNotificationPermission by remember {
|
var hasNotificationPermission by remember {
|
||||||
@@ -226,11 +259,18 @@ fun SettingsScreen(
|
|||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = "ISO Drive",
|
text = "ISO Droid",
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
|
val versionName = remember {
|
||||||
|
try {
|
||||||
|
context.packageManager.getPackageInfo(context.packageName, 0).versionName
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = "Version 1.0",
|
text = "Version $versionName",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -254,13 +294,13 @@ fun SettingsScreen(
|
|||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "git.shihaam.dev/shihaam/ISODrive",
|
text = "git.shihaam.dev/shihaam/ISODroid",
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
textDecoration = TextDecoration.Underline
|
textDecoration = TextDecoration.Underline
|
||||||
),
|
),
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
openUrl("https://git.shihaam.dev/shihaam/ISODrive")
|
openUrl("https://git.shihaam.dev/shihaam/ISODroid")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -301,7 +341,7 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = "The CLI tool that powers ISO Drive. Mounts ISO/IMG files as bootable USB devices using configfs.",
|
text = "The CLI tool that powers ISO Droid. Mounts ISO/IMG files as bootable USB devices using configfs.",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(top = 8.dp)
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
@@ -364,40 +404,394 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path edit dialog
|
// Directory browser dialog
|
||||||
if (showPathDialog) {
|
if (showPathDialog) {
|
||||||
|
DirectoryBrowserDialog(
|
||||||
|
initialPath = uiState.isoDirectory,
|
||||||
|
onDismiss = { showPathDialog = false },
|
||||||
|
onSelect = { selectedPath ->
|
||||||
|
viewModel.setIsoDirectory(selectedPath)
|
||||||
|
showPathDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class BrowserItem(
|
||||||
|
val name: String,
|
||||||
|
val isDirectory: Boolean,
|
||||||
|
val fullPath: String,
|
||||||
|
val isDeletable: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun DirectoryBrowserDialog(
|
||||||
|
initialPath: String,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSelect: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val hapticFeedback = LocalHapticFeedback.current
|
||||||
|
var currentPath by remember { mutableStateOf(initialPath) }
|
||||||
|
var items by remember { mutableStateOf<List<BrowserItem>>(emptyList()) }
|
||||||
|
var isLoading by remember { mutableStateOf(true) }
|
||||||
|
var showCreateFolderDialog by remember { mutableStateOf(false) }
|
||||||
|
var showDeleteDialog by remember { mutableStateOf<String?>(null) }
|
||||||
|
var newFolderName by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val storageRoot = Environment.getExternalStorageDirectory().absolutePath
|
||||||
|
|
||||||
|
fun loadContents(path: String) {
|
||||||
|
scope.launch {
|
||||||
|
isLoading = true
|
||||||
|
// Load directories
|
||||||
|
val dirResult = RootManager.executeCommand(
|
||||||
|
"find \"$path\" -maxdepth 1 -mindepth 1 -type d 2>/dev/null"
|
||||||
|
)
|
||||||
|
val directories = if (dirResult.success && dirResult.output.isNotBlank()) {
|
||||||
|
dirResult.output.lines()
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { !it.substringAfterLast("/").startsWith(".") }
|
||||||
|
.map { dirPath ->
|
||||||
|
// Check if this directory was created by the app (has .isodroiddir marker)
|
||||||
|
val markerCheck = RootManager.executeCommand(
|
||||||
|
"test -f \"$dirPath/.isodroiddir\" && echo 'yes' || echo 'no'"
|
||||||
|
)
|
||||||
|
val isDeletable = markerCheck.output.trim() == "yes"
|
||||||
|
BrowserItem(dirPath.substringAfterLast("/"), true, dirPath, isDeletable)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load ISO/IMG files
|
||||||
|
val fileResult = RootManager.executeCommand(
|
||||||
|
"find \"$path\" -maxdepth 1 -type f \\( -iname '*.iso' -o -iname '*.img' \\) 2>/dev/null"
|
||||||
|
)
|
||||||
|
val files = if (fileResult.success && fileResult.output.isNotBlank()) {
|
||||||
|
fileResult.output.lines()
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { it.trim() }
|
||||||
|
.map { BrowserItem(it.substringAfterLast("/"), false, it) }
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
items = (directories.sortedBy { it.name.lowercase() } + files.sortedBy { it.name.lowercase() })
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFolder(name: String) {
|
||||||
|
scope.launch {
|
||||||
|
val trimmedName = name.trim()
|
||||||
|
if (trimmedName.isEmpty()) return@launch
|
||||||
|
|
||||||
|
val newPath = "$currentPath/$trimmedName"
|
||||||
|
RootManager.executeCommand("mkdir -p \"$newPath\"")
|
||||||
|
// Create marker file to indicate this folder was created by the app
|
||||||
|
RootManager.executeCommand("touch \"$newPath/.isodroiddir\"")
|
||||||
|
// Auto-navigate into the new folder
|
||||||
|
currentPath = newPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteFolder(path: String) {
|
||||||
|
scope.launch {
|
||||||
|
RootManager.executeCommand("rm -rf \"$path\"")
|
||||||
|
loadContents(currentPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(currentPath) {
|
||||||
|
loadContents(currentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text("Select Directory")
|
||||||
|
IconButton(onClick = { showCreateFolderDialog = true }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.CreateNewFolder,
|
||||||
|
contentDescription = "Create directory",
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
// Current path display
|
||||||
|
Text(
|
||||||
|
text = currentPath,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
// Content list
|
||||||
|
if (isLoading) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 200.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.size(32.dp))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 200.dp, max = 300.dp)
|
||||||
|
) {
|
||||||
|
// Parent directory (..)
|
||||||
|
if (currentPath != "/" && currentPath != storageRoot) {
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
val parent = currentPath.substringBeforeLast("/")
|
||||||
|
currentPath = parent.ifEmpty { "/" }
|
||||||
|
}
|
||||||
|
.padding(vertical = 12.dp, horizontal = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Folder,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Text(
|
||||||
|
text = "..",
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items (directories and files)
|
||||||
|
items(items) { item ->
|
||||||
|
val isIso = item.name.lowercase().endsWith(".iso")
|
||||||
|
val isImg = item.name.lowercase().endsWith(".img")
|
||||||
|
val osIcon = if (!item.isDirectory) findOsIcon(context, item.name) else null
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
if (item.isDirectory) {
|
||||||
|
currentPath = item.fullPath
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
if (item.isDirectory) {
|
||||||
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
when {
|
||||||
|
item.fullPath == initialPath -> {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Can't delete your current ISO directory. Change it first, then try again.",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
item.isDeletable -> {
|
||||||
|
showDeleteDialog = item.fullPath
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Can't delete this directory — it wasn't created by ISO Droid",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Icon with badge
|
||||||
|
Box(modifier = Modifier.size(32.dp)) {
|
||||||
|
if (item.isDirectory) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Folder,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
} else if (osIcon != null) {
|
||||||
|
// OS icon with file type badge
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(context)
|
||||||
|
.data("file:///android_asset/osicons/$osIcon")
|
||||||
|
.decoderFactory(SvgDecoder.Factory())
|
||||||
|
.build(),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary)
|
||||||
|
)
|
||||||
|
// File type badge
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isIso) Icons.Default.Album else Icons.AutoMirrored.Filled.InsertDriveFile,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(14.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.offset(x = 2.dp, y = 2.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
.padding(2.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Fallback icon
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isIso) Icons.Default.Album else Icons.AutoMirrored.Filled.InsertDriveFile,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Text(
|
||||||
|
text = item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = if (item.isDirectory)
|
||||||
|
MaterialTheme.colorScheme.onSurface
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty state
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "Empty directory",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(vertical = 16.dp, horizontal = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onSelect(currentPath) }) {
|
||||||
|
Text("Select")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create folder dialog
|
||||||
|
if (showCreateFolderDialog) {
|
||||||
|
val hasInvalidChars = newFolderName.any { !it.isLetterOrDigit() && it !in "-_. ()[]+," }
|
||||||
|
val isValidInput = newFolderName.isNotBlank() && !hasInvalidChars
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showPathDialog = false },
|
onDismissRequest = {
|
||||||
title = { Text("ISO Directory") },
|
showCreateFolderDialog = false
|
||||||
|
newFolderName = ""
|
||||||
|
},
|
||||||
|
title = { Text("Create Directory") },
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
|
||||||
text = "Enter the path to the directory containing your ISO/IMG files.",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = tempPath,
|
value = newFolderName,
|
||||||
onValueChange = { tempPath = it },
|
onValueChange = { newFolderName = it },
|
||||||
label = { Text("Path") },
|
label = { Text("Directory name") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
isError = hasInvalidChars,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (hasInvalidChars) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Invalid directory name",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setIsoDirectory(tempPath)
|
if (isValidInput) {
|
||||||
showPathDialog = false
|
createFolder(newFolderName)
|
||||||
}
|
showCreateFolderDialog = false
|
||||||
|
newFolderName = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = isValidInput
|
||||||
) {
|
) {
|
||||||
Text("Save")
|
Text("Create")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showPathDialog = false }) {
|
TextButton(onClick = {
|
||||||
|
showCreateFolderDialog = false
|
||||||
|
newFolderName = ""
|
||||||
|
}) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete confirmation dialog
|
||||||
|
showDeleteDialog?.let { pathToDelete ->
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDeleteDialog = null },
|
||||||
|
title = { Text("Delete Directory") },
|
||||||
|
text = {
|
||||||
|
Text("Are you sure you want to delete \"${pathToDelete.substringAfterLast("/")}\" and all its contents?")
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
deleteFolder(pathToDelete)
|
||||||
|
showDeleteDialog = null
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Delete", color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showDeleteDialog = null }) {
|
||||||
Text("Cancel")
|
Text("Cancel")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.screens
|
package sh.sar.isodroid.ui.screens
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@@ -92,7 +98,7 @@ private fun WelcomeStep(
|
|||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Welcome to ISO Drive",
|
text = "Welcome to ISO Droid",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
@@ -147,7 +153,7 @@ private fun RootAccessStep(
|
|||||||
title = "Root Access",
|
title = "Root Access",
|
||||||
description = when (hasRoot) {
|
description = when (hasRoot) {
|
||||||
false -> "Root access was denied. The app requires root to mount ISO files. You can try again or skip and grant access later from your root manager."
|
false -> "Root access was denied. The app requires root to mount ISO files. You can try again or skip and grant access later from your root manager."
|
||||||
else -> "ISO Drive needs superuser (root) access to mount ISO files as USB devices. This is required because mounting USB gadgets is a system-level operation."
|
else -> "ISO Droid needs superuser (root) access to mount ISO files as USB devices. This is required because mounting USB gadgets is a system-level operation."
|
||||||
},
|
},
|
||||||
granted = hasRoot
|
granted = hasRoot
|
||||||
)
|
)
|
||||||
@@ -253,7 +259,7 @@ private fun NotificationStep(
|
|||||||
description = when {
|
description = when {
|
||||||
permanentlyDenied -> "Notification permission was denied. You can enable it later in system settings if you change your mind."
|
permanentlyDenied -> "Notification permission was denied. You can enable it later in system settings if you change your mind."
|
||||||
wasDenied -> "Notification permission was denied. You can try again or continue without notifications."
|
wasDenied -> "Notification permission was denied. You can try again or continue without notifications."
|
||||||
else -> "ISO Drive shows a notification when an ISO is mounted, with a quick unmount button. This helps you keep track of the mount status."
|
else -> "ISO Droid shows a notification when an ISO is mounted, with a quick unmount button. This helps you keep track of the mount status."
|
||||||
},
|
},
|
||||||
granted = if (wasDenied) false else if (hasPermission) true else null
|
granted = if (wasDenied) false else if (hasPermission) true else null
|
||||||
)
|
)
|
||||||
@@ -318,7 +324,7 @@ private fun CompleteStep(
|
|||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "ISO Drive is ready to use. Place your ISO or IMG files in the isodrive folder and start mounting.",
|
text = "ISO Droid is ready to use. Place your ISO or IMG files in the isodroid directory and start mounting.",
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
@@ -327,7 +333,7 @@ private fun CompleteStep(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Default directory: /sdcard/isodrive/",
|
text = "Default directory: ${Environment.getExternalStorageDirectory().absolutePath}/isodroid/",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||||
@@ -339,7 +345,7 @@ private fun CompleteStep(
|
|||||||
onClick = onFinish,
|
onClick = onFinish,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text("Start Using ISO Drive")
|
Text("Start Using ISO Droid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.theme
|
package sh.sar.isodroid.ui.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.theme
|
package sh.sar.isodroid.ui.theme
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.ui.theme
|
package sh.sar.isodroid.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Shiham Abdul Rahman <shihaam@shihaam.dev>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
package sh.sar.isodroid.viewmodel
|
package sh.sar.isodroid.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
@@ -47,7 +52,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory")
|
private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory")
|
||||||
private val DEFAULT_ISO_DIRECTORY = "${Environment.getExternalStorageDirectory().absolutePath}/isodrive"
|
private val DEFAULT_ISO_DIRECTORY = "${Environment.getExternalStorageDirectory().absolutePath}/isodroid"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var initialized = false
|
private var initialized = false
|
||||||
@@ -201,6 +206,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Create directory if it doesn't exist
|
// Create directory if it doesn't exist
|
||||||
if (!directory.exists()) {
|
if (!directory.exists()) {
|
||||||
RootManager.executeCommand("mkdir -p \"$currentPath\"")
|
RootManager.executeCommand("mkdir -p \"$currentPath\"")
|
||||||
|
// Create marker file to indicate this folder was created by the app
|
||||||
|
RootManager.executeCommand("touch \"$currentPath/.isodroiddir\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try multiple methods to list files
|
// Try multiple methods to list files
|
||||||
|
|||||||
@@ -4,167 +4,10 @@
|
|||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
|
|
||||||
|
<!-- ISODroid Background - Clean Android Green -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#3DDC84"
|
android:fillColor="#3DDC84"
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,30 +1,99 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
||||||
<aapt:attr name="android:fillColor">
|
<!-- ISODroid Launcher Icon Foreground -->
|
||||||
<gradient
|
<!-- Safe zone is 66dp centered (21-87 in 108dp viewport) -->
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
<!-- Left antenna with ball -->
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="#FFFFFF"
|
||||||
android:fillType="nonZero"
|
android:pathData="M38,24 a3,3 0 1,1 0.01,0"/>
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
<path
|
||||||
android:strokeWidth="1"
|
android:fillColor="#FFFFFF"
|
||||||
android:strokeColor="#00000000" />
|
android:strokeColor="#FFFFFF"
|
||||||
</vector>
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M39.5,26.5 L44,33"/>
|
||||||
|
|
||||||
|
<!-- Right antenna with ball -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M70,24 a3,3 0 1,1 0.01,0"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M68.5,26.5 L64,33"/>
|
||||||
|
|
||||||
|
<!-- Droid head -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M34,42 Q34,30 54,30 Q74,30 74,42 L74,56 Q74,62 68,62 L40,62 Q34,62 34,56 Z"/>
|
||||||
|
|
||||||
|
<!-- Left eye -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M44,46 a4,4 0 1,0 0.01,0"/>
|
||||||
|
|
||||||
|
<!-- Right eye -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M64,46 a4,4 0 1,0 0.01,0"/>
|
||||||
|
|
||||||
|
<!-- USB Trident Symbol -->
|
||||||
|
<!-- Main vertical stem -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M54,62 L54,88"/>
|
||||||
|
|
||||||
|
<!-- Horizontal bar -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M40,70 L68,70"/>
|
||||||
|
|
||||||
|
<!-- Left branch -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M40,70 L40,76"/>
|
||||||
|
|
||||||
|
<!-- Left terminal (rectangle) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M36,76 L44,76 L44,82 L36,82 Z"/>
|
||||||
|
|
||||||
|
<!-- Right branch -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M68,70 L68,76"/>
|
||||||
|
|
||||||
|
<!-- Right terminal (circle) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M68,80 a4,4 0 1,0 0.01,0"/>
|
||||||
|
|
||||||
|
<!-- USB bottom arrow -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:pathData="M48,88 L54,94 L60,88"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
|
||||||
|
<!-- ISODroid Monochrome Icon for Material You Theming -->
|
||||||
|
<!-- This icon uses a single color that Android will tint based on wallpaper -->
|
||||||
|
|
||||||
|
<!-- Left antenna with ball -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M38,24 a3,3 0 1,1 0.01,0"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M39.5,26.5 L44,33"/>
|
||||||
|
|
||||||
|
<!-- Right antenna with ball -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M70,24 a3,3 0 1,1 0.01,0"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M68.5,26.5 L64,33"/>
|
||||||
|
|
||||||
|
<!-- Droid head with hollow eyes -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M34,42 Q34,30 54,30 Q74,30 74,42 L74,56 Q74,62 68,62 L40,62 Q34,62 34,56 Z M44,50 a4,4 0 1,0 0.01,0 M64,50 a4,4 0 1,0 0.01,0"/>
|
||||||
|
|
||||||
|
<!-- USB Trident Symbol -->
|
||||||
|
<!-- Main vertical stem -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M54,62 L54,88"/>
|
||||||
|
|
||||||
|
<!-- Horizontal bar -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M40,70 L68,70"/>
|
||||||
|
|
||||||
|
<!-- Left branch -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M40,70 L40,76"/>
|
||||||
|
|
||||||
|
<!-- Left terminal (rectangle) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M36,76 L44,76 L44,82 L36,82 Z"/>
|
||||||
|
|
||||||
|
<!-- Right branch -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="3.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M68,70 L68,76"/>
|
||||||
|
|
||||||
|
<!-- Right terminal (circle) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M68,80 a4,4 0 1,0 0.01,0"/>
|
||||||
|
|
||||||
|
<!-- USB bottom arrow -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:pathData="M48,88 L54,94 L60,88"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -4,7 +4,67 @@
|
|||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:tint="?attr/colorControlNormal">
|
android:tint="?attr/colorControlNormal">
|
||||||
|
|
||||||
|
<!-- ISODroid Notification Icon -->
|
||||||
|
|
||||||
|
<!-- Left antenna ball -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,16.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zM12,9.5c-1.38,0 -2.5,1.12 -2.5,2.5s1.12,2.5 2.5,2.5 2.5,-1.12 2.5,-2.5 -1.12,-2.5 -2.5,-2.5z"/>
|
android:pathData="M7.5,2.5 m-0.9,0 a0.9,0.9 0 1,1 1.8,0 a0.9,0.9 0 1,1 -1.8,0"/>
|
||||||
|
|
||||||
|
<!-- Left antenna stem -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M7.8,3.3 L8.8,3.8 L9.3,5.3 L8.3,5.5 Z"/>
|
||||||
|
|
||||||
|
<!-- Right antenna ball -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M16.5,2.5 m-0.9,0 a0.9,0.9 0 1,1 1.8,0 a0.9,0.9 0 1,1 -1.8,0"/>
|
||||||
|
|
||||||
|
<!-- Right antenna stem -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M16.2,3.3 L15.2,3.8 L14.7,5.3 L15.7,5.5 Z"/>
|
||||||
|
|
||||||
|
<!-- Droid head -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8,8 Q8,5.2 12,5.2 Q16,5.2 16,8 L16,11 Q16,12.5 14.5,12.5 L9.5,12.5 Q8,12.5 8,11 Z"/>
|
||||||
|
|
||||||
|
<!-- USB main stem -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M11.2,12.5 L12.8,12.5 L12.8,20 L11.2,20 Z"/>
|
||||||
|
|
||||||
|
<!-- USB horizontal bar -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M9,14.8 L15,14.8 L15,16 L9,16 Z"/>
|
||||||
|
|
||||||
|
<!-- USB left branch vertical -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8.5,16 L9.6,16 L9.6,17 L8.5,17 Z"/>
|
||||||
|
|
||||||
|
<!-- USB left terminal rectangle -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8,17 L10.2,17 L10.2,19.2 L8,19.2 Z"/>
|
||||||
|
|
||||||
|
<!-- USB right branch vertical -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M14.4,16 L15.5,16 L15.5,17.5 L14.4,17.5 Z"/>
|
||||||
|
|
||||||
|
<!-- USB right terminal circle -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M15,18.5 m-1.2,0 a1.2,1.2 0 1,1 2.4,0 a1.2,1.2 0 1,1 -2.4,0"/>
|
||||||
|
|
||||||
|
<!-- USB bottom arrow -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M10.5,20 L12,22 L13.5,20 Z"/>
|
||||||
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Build isodrive from source for all Android architectures
|
||||||
|
# Requires: Android NDK (or runs via nix-shell on NixOS)
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname "$(realpath "$0")")
|
||||||
|
ISODRIVE_DIR="/tmp/isodrive"
|
||||||
|
OUTPUT_DIR="$SCRIPT_DIR/app/src/main/assets/bin"
|
||||||
|
|
||||||
|
# Clone isodrive source
|
||||||
|
if [[ -d "$ISODRIVE_DIR" ]]; then
|
||||||
|
echo "Updating isodrive source..."
|
||||||
|
git -C "$ISODRIVE_DIR" pull
|
||||||
|
else
|
||||||
|
echo "Cloning isodrive..."
|
||||||
|
git clone --depth 1 https://github.com/nitanmarcel/isodrive "$ISODRIVE_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SRCS="$ISODRIVE_DIR/src/util.cpp $ISODRIVE_DIR/src/configfsisomanager.cpp $ISODRIVE_DIR/src/androidusbisomanager.cpp $ISODRIVE_DIR/src/main.cpp"
|
||||||
|
CFLAGS="-I$ISODRIVE_DIR/src/include -static-libstdc++ -Os -s"
|
||||||
|
|
||||||
|
build_all() {
|
||||||
|
local NDK="$1"
|
||||||
|
local TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||||
|
|
||||||
|
echo "Building arm64-v8a..."
|
||||||
|
"$TOOLCHAIN/aarch64-linux-android26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/arm64-v8a/isodrive"
|
||||||
|
|
||||||
|
echo "Building armeabi-v7a..."
|
||||||
|
"$TOOLCHAIN/armv7a-linux-androideabi26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/armeabi-v7a/isodrive"
|
||||||
|
|
||||||
|
echo "Building x86_64..."
|
||||||
|
"$TOOLCHAIN/x86_64-linux-android26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/x86_64/isodrive"
|
||||||
|
|
||||||
|
echo "Building x86..."
|
||||||
|
"$TOOLCHAIN/i686-linux-android26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/x86/isodrive"
|
||||||
|
|
||||||
|
echo "Done! Built isodrive for all architectures."
|
||||||
|
ls -la "$OUTPUT_DIR"/*/isodrive
|
||||||
|
}
|
||||||
|
|
||||||
|
# On NixOS, prefer nix-shell (local Android SDK has /bin/bash issues)
|
||||||
|
if command -v nix-shell &>/dev/null; then
|
||||||
|
echo "Using nix-shell to get Android NDK..."
|
||||||
|
export SRCS CFLAGS OUTPUT_DIR
|
||||||
|
NIXPKGS_ALLOW_UNFREE=1 nix-shell -p androidenv.androidPkgs.ndk-bundle --run '
|
||||||
|
SDK_ROOT=$(find /nix/store -maxdepth 1 -name "*android-sdk-ndk*" -type d 2>/dev/null | head -1)
|
||||||
|
NDK=$(ls -d "$SDK_ROOT/libexec/android-sdk/ndk/"* | head -1)
|
||||||
|
TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||||
|
|
||||||
|
echo "Using NDK: $NDK"
|
||||||
|
|
||||||
|
echo "Building arm64-v8a..."
|
||||||
|
"$TOOLCHAIN/aarch64-linux-android26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/arm64-v8a/isodrive"
|
||||||
|
|
||||||
|
echo "Building armeabi-v7a..."
|
||||||
|
"$TOOLCHAIN/armv7a-linux-androideabi26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/armeabi-v7a/isodrive"
|
||||||
|
|
||||||
|
echo "Building x86_64..."
|
||||||
|
"$TOOLCHAIN/x86_64-linux-android26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/x86_64/isodrive"
|
||||||
|
|
||||||
|
echo "Building x86..."
|
||||||
|
"$TOOLCHAIN/i686-linux-android26-clang++" $CFLAGS $SRCS -o "$OUTPUT_DIR/x86/isodrive"
|
||||||
|
|
||||||
|
echo "Done!"
|
||||||
|
ls -la "$OUTPUT_DIR"/*/isodrive
|
||||||
|
'
|
||||||
|
elif [[ -n "${ANDROID_NDK_HOME:-}" ]]; then
|
||||||
|
build_all "$ANDROID_NDK_HOME"
|
||||||
|
elif [[ -n "${ANDROID_NDK:-}" ]]; then
|
||||||
|
build_all "$ANDROID_NDK"
|
||||||
|
else
|
||||||
|
echo "Error: Android NDK not found."
|
||||||
|
echo "Set ANDROID_NDK_HOME or ANDROID_NDK, or install nix-shell."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -229,13 +229,13 @@ fun toCommandArgs(): List<String> {
|
|||||||
**Example commands:**
|
**Example commands:**
|
||||||
```bash
|
```bash
|
||||||
# Mount as read-only mass storage
|
# Mount as read-only mass storage
|
||||||
isodrive "/sdcard/isodrive/ubuntu.iso" -configfs
|
isodrive "/sdcard/isodroid/ubuntu.iso" -configfs
|
||||||
|
|
||||||
# Mount as writable drive
|
# Mount as writable drive
|
||||||
isodrive "/sdcard/isodrive/drive.img" -rw -configfs
|
isodrive "/sdcard/isodroid/drive.img" -rw -configfs
|
||||||
|
|
||||||
# Mount as CD-ROM
|
# Mount as CD-ROM
|
||||||
isodrive "/sdcard/isodrive/windows.iso" -cdrom -configfs
|
isodrive "/sdcard/isodroid/windows.iso" -cdrom -configfs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Event System
|
## Event System
|
||||||
@@ -338,7 +338,7 @@ private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory")
|
|||||||
```
|
```
|
||||||
|
|
||||||
Stores:
|
Stores:
|
||||||
- Custom ISO directory path (default: `/sdcard/isodrive/`)
|
- Custom ISO directory path (default: `/sdcard/isodroid/`)
|
||||||
|
|
||||||
### SharedPreferences
|
### SharedPreferences
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
* New app icon
|
||||||
|
* Mount ISO/IMG files as USB mass storage or CD-ROM
|
||||||
|
* Create blank IMG files for writable USB drives
|
||||||
|
* Read-only or read-write mount options
|
||||||
|
* Persistent notification with quick unmount button
|
||||||
|
* OS-specific icons for popular distributions
|
||||||
|
* Download links to popular operating systems
|
||||||
|
* Rename and delete files
|
||||||
|
* Material 3 dynamic theming with dark mode support
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* Fix app name displaying as "ISO Drive" instead of "ISO Droid"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* Fix default ISO directory path to /sdcard/isodroid
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
* Directory browser for changing ISO directory in settings
|
||||||
|
* Create new directories from the directory browser
|
||||||
|
* Delete directories created by the app (long press)
|
||||||
|
* Shows ISO/IMG files with OS icons in directory browser
|
||||||
|
* Empty state on home screen now shows current path and helpful hints
|
||||||
|
* Version number now read dynamically from app config
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
ISO Droid lets you mount ISO and IMG files as USB mass storage or CD-ROM devices on rooted Android devices. Connect your phone to any PC and it will appear as a bootable USB drive or CD-ROM.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* Mount ISO/IMG files as USB mass storage or CD-ROM
|
||||||
|
* Create blank IMG files for writable USB drives
|
||||||
|
* Read-only or read-write mount options
|
||||||
|
* Persistent notification with quick unmount button
|
||||||
|
* OS-specific icons for popular distributions (Linux, BSD, etc.)
|
||||||
|
* Download links to popular operating systems
|
||||||
|
* Rename and delete files
|
||||||
|
* Material 3 dynamic theming with dark mode support
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
* Rooted Android device (Magisk, KernelSU, APatch, etc.)
|
||||||
|
* USB gadget support (configfs or sysfs)
|
||||||
|
* Android 8.0+ (API 26)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
1. Place your ISO/IMG files in /sdcard/isodroid/
|
||||||
|
2. Select an ISO/IMG file from the list
|
||||||
|
3. Choose mount options (Mass Storage or CD-ROM)
|
||||||
|
4. Tap Mount
|
||||||
|
5. Connect your phone to a PC via USB
|
||||||
|
6. Unmount via the app or notification when done
|
||||||
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 124 KiB |
@@ -0,0 +1 @@
|
|||||||
|
Mount ISO/IMG files as USB drives on rooted Android
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ISO Droid
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Get the directory where this script lives
|
||||||
|
SCRIPT_DIR=$(dirname "$(realpath "$0")")
|
||||||
|
|
||||||
|
ISODRIVE_VERSION=$(curl -sI https://github.com/nitanmarcel/isodrive-magisk/releases/latest | grep -i ^location | grep -oP 'v\K[\d.]+')
|
||||||
|
curl -sL https://github.com/nitanmarcel/isodrive-magisk/releases/download/v$ISODRIVE_VERSION/isodrive-magisk-v$ISODRIVE_VERSION.zip -o /tmp/isodrive-magisk.zip
|
||||||
|
|
||||||
|
unzip -q /tmp/isodrive-magisk.zip -d /tmp/isodrive-magisk
|
||||||
|
|
||||||
|
# Move the isodrive binary for each architecture
|
||||||
|
mv /tmp/isodrive-magisk/libs/arm64-v8a/isodrive $SCRIPT_DIR/app/src/main/assets/bin/arm64-v8a/
|
||||||
|
mv /tmp/isodrive-magisk/libs/armeabi-v7a/isodrive $SCRIPT_DIR/app/src/main/assets/bin/armeabi-v7a/
|
||||||
|
mv /tmp/isodrive-magisk/libs/x86/isodrive $SCRIPT_DIR/app/src/main/assets/bin/x86/
|
||||||
|
mv /tmp/isodrive-magisk/libs/x86_64/isodrive $SCRIPT_DIR/app/src/main/assets/bin/x86_64/
|
||||||
|
|
||||||
|
# Clean up temp files
|
||||||
|
rm -rf /tmp/isodrive-magisk /tmp/isodrive-magisk.zip
|
||||||
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,44 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 108" width="512" height="512">
|
||||||
|
<!-- Background -->
|
||||||
|
<rect width="108" height="108" rx="20" fill="#3DDC84"/>
|
||||||
|
|
||||||
|
<!-- Left antenna ball -->
|
||||||
|
<circle cx="38" cy="24" r="3" fill="#FFFFFF"/>
|
||||||
|
<!-- Left antenna stem -->
|
||||||
|
<line x1="39.5" y1="26.5" x2="44" y2="33" stroke="#FFFFFF" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Right antenna ball -->
|
||||||
|
<circle cx="70" cy="24" r="3" fill="#FFFFFF"/>
|
||||||
|
<!-- Right antenna stem -->
|
||||||
|
<line x1="68.5" y1="26.5" x2="64" y2="33" stroke="#FFFFFF" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Droid head -->
|
||||||
|
<path d="M34,42 Q34,30 54,30 Q74,30 74,42 L74,56 Q74,62 68,62 L40,62 Q34,62 34,56 Z" fill="#FFFFFF"/>
|
||||||
|
|
||||||
|
<!-- Left eye -->
|
||||||
|
<circle cx="44" cy="46" r="4" fill="#3DDC84"/>
|
||||||
|
|
||||||
|
<!-- Right eye -->
|
||||||
|
<circle cx="64" cy="46" r="4" fill="#3DDC84"/>
|
||||||
|
|
||||||
|
<!-- USB main stem -->
|
||||||
|
<line x1="54" y1="62" x2="54" y2="88" stroke="#FFFFFF" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- USB horizontal bar -->
|
||||||
|
<line x1="40" y1="70" x2="68" y2="70" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- USB left branch -->
|
||||||
|
<line x1="40" y1="70" x2="40" y2="76" stroke="#FFFFFF" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- USB left terminal rectangle -->
|
||||||
|
<rect x="36" y="76" width="8" height="6" fill="#FFFFFF"/>
|
||||||
|
|
||||||
|
<!-- USB right branch -->
|
||||||
|
<line x1="68" y1="70" x2="68" y2="76" stroke="#FFFFFF" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- USB right terminal circle -->
|
||||||
|
<circle cx="68" cy="80" r="4" fill="#FFFFFF"/>
|
||||||
|
|
||||||
|
<!-- USB bottom arrow -->
|
||||||
|
<path d="M48,88 L54,94 L60,88" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |