{"id":50704490,"url":"https://github.com/sudo-py-dev/saveto","last_synced_at":"2026-06-09T11:00:27.429Z","repository":{"id":363502323,"uuid":"1263626198","full_name":"sudo-py-dev/SaveTo","owner":"sudo-py-dev","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-09T07:36:04.000Z","size":86,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T08:14:20.323Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sudo-py-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-09T05:57:48.000Z","updated_at":"2026-06-09T07:36:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sudo-py-dev/SaveTo","commit_stats":null,"previous_names":["sudo-py-dev/saveto"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sudo-py-dev/SaveTo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sudo-py-dev%2FSaveTo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sudo-py-dev%2FSaveTo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sudo-py-dev%2FSaveTo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sudo-py-dev%2FSaveTo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sudo-py-dev","download_url":"https://codeload.github.com/sudo-py-dev/SaveTo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sudo-py-dev%2FSaveTo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34103357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T11:00:16.633Z","updated_at":"2026-06-09T11:00:27.406Z","avatar_url":"https://github.com/sudo-py-dev.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"graphics/app_icon.svg\" width=\"128\" height=\"128\" alt=\"SaveTo App Icon\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eSaveTo\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/sudo-py-dev/SaveTo/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/sudo-py-dev/SaveTo/actions/workflows/ci.yml/badge.svg\" alt=\"Android CI\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/sudo-py-dev/SaveTo/releases\"\u003e\u003cimg src=\"https://img.shields.io/badge/release-v1.1-blue.svg?logo=github\" alt=\"GitHub Release\" /\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-Apache--2.0-orange.svg\" alt=\"License\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://android-arsenal.com/api?level=23\"\u003e\u003cimg src=\"https://img.shields.io/badge/API-23%2B-brightgreen.svg?style=flat\u0026logo=android\" alt=\"API Level\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  SaveTo is a lightweight, single-purpose Android utility that integrates into the system-wide sharing sheet to save shared files, multiple documents, or text directly to any destination chosen via the Storage Access Framework (SAF).\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/sudo-py-dev/SaveTo/releases/latest/download/app-foss-release.apk\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Download-FOSS%20APK-orange?style=for-the-badge\u0026logo=android\u0026logoColor=white\" height=\"40\" alt=\"Download FOSS APK\" /\u003e\n  \u003c/a\u003e\n  \u0026nbsp;\u0026nbsp;\n  \u003ca href=\"https://github.com/sudo-py-dev/SaveTo/releases/latest\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Download_on-GitHub-black?style=for-the-badge\u0026logo=github\u0026logoColor=white\" height=\"40\" alt=\"Download on GitHub\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Visual Preview\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"graphics/feature_graphic.png\" width=\"800\" alt=\"SaveTo Feature Graphic\" /\u003e\n\u003c/p\u003e\n\n| 1. Share Sheet Integration | 2. Save Location Selection |\n| :-: | :-: |\n| \u003cimg src=\"graphics/phone_screenshot_1_share_sheet.png\" width=\"350\" alt=\"Share Sheet\" /\u003e | \u003cimg src=\"graphics/phone_screenshot_2_save_picker.png\" width=\"350\" alt=\"Save Picker\" /\u003e |\n\n---\n\n## Key Features\n\n- **Seamless Share Target**: Integrates directly with Android's system share sheet, registering for `android.intent.action.SEND` and `android.intent.action.SEND_MULTIPLE` across all MIME types (`*/*`).\n- **No Launcher Clutter**: The application does not declare a launcher activity, keeping the launcher interface clean. It is active only when files are shared.\n- **Privacy \u0026 Security First**: Zero dangerous storage permissions (such as `READ_EXTERNAL_STORAGE` or `WRITE_EXTERNAL_STORAGE`) are required. It relies entirely on temporary URI permissions granted dynamically via incoming Intents.\n- **Bulk Save support**: When multiple files are shared, it lets you select a destination directory and saves all files in one operation with a progress tracking indicator.\n- **Text-to-File Conversion**: Automatically detects shared text content, converts it into a `.txt` file, and prompts to save it.\n- **Robust Error Handling**: Safely reports partial failures when bulk-saving multiple files, handles I/O exceptions, and cleans up temporary cache files.\n\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003e🛠️ Developer \u0026 Build Guide\u003c/summary\u003e\n\n  ### Technical Architecture\n\n  The codebase is structured following clean Android architecture principles, utilizing Kotlin, Coroutines, and Android Jetpack Architecture Components.\n\n  ```mermaid\n  graph TD\n      Intent[Incoming Share Intent] --\u003e |SEND / SEND_MULTIPLE| Activity[ShareActivity]\n      Activity --\u003e |Resolve Metadata| VM[SaveViewModel]\n      Activity --\u003e |Launch Picker| SAF[Storage Access Framework]\n      SAF --\u003e |Dest URI| VM\n      VM --\u003e |Coroutines IO Dispatcher| Save[Buffered Stream Write]\n  ```\n\n  ### Components\n\n  - **[AndroidManifest.xml](app/src/main/AndroidManifest.xml)**: Configures the intent filters, theme settings, and application metadata. `ShareActivity` is configured with a translucent theme (`Theme.Translucent.NoTitleBar`) so that it overlays seamlessly onto the system share dialog.\n  - **[ShareActivity.kt](app/src/main/kotlin/com/save/to/ShareActivity.kt)**: Manages UI, checks URI read access, interacts with the Android Storage Access Framework (using `ActivityResultContracts.CreateDocument` and `OpenDocumentTree`), and shows standard system alerts and progress dialogs.\n  - **[SaveViewModel.kt](app/src/main/kotlin/com/save/to/SaveViewModel.kt)**: Houses the business logic for resolving source file details, performing high-performance I/O operations using a buffered stream (`8KB` buffer size), managing state, and cleaning up cache files.\n  - **[strings.xml](app/src/main/res/values/strings.xml)**: Declares all UI string resources and error messages for user-facing localization.\n\n  ### Development \u0026 Build Requirements\n\n  - **SDK Targets**: Min SDK `23` (Android 6.0), Target SDK `35` (Android 15)\n  - **Java Compatibility**: JDK `17` toolchain\n  - **Build Tool**: Gradle Kotlin DSL\n\n  ### Code Styling and Design\n\n  This application strictly follows modern development standards:\n  - **Coroutines for Asynchronous I/O**: Offloads stream writing tasks to `Dispatchers.IO` and uses `ensureActive()` inside loops to respect lifecycle cancellation.\n  - **No Force Unwraps**: Handles null values gracefully and relies on defensive typing contracts to prevent runtime crashes.\n  - **Resource Cleanup**: Employs `use { ... }` blocks for Kotlin `Closeable` objects, ensuring input and output streams are safely closed under all circumstances.\n\n  ### Building the Project\n\n  Ensure you have a valid `keystore.properties` file configured in the root directory prior to building. Run the following command from the project root to compile a release build:\n\n  ```bash\n  ./gradlew assembleRelease\n  ```\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsudo-py-dev%2Fsaveto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsudo-py-dev%2Fsaveto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsudo-py-dev%2Fsaveto/lists"}