diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ecce60182..13340f070 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -@ShiftHackZ +@crim50n diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7dcfcb390..52fbcc080 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,11 +1 @@ -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: ['https://www.buymeacoffee.com/shifthackz', 'https://send.monobank.ua/jar/3n2Aj3Hv3g'] +# Funding links removed diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..8679c5370 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Build Release APKs + +on: + workflow_dispatch: + +jobs: + build-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4.1.0 + + - name: Extract version from libs.versions.toml + id: version + run: | + VERSION=$(grep 'versionName = ' gradle/libs.versions.toml | sed 's/versionName = "\(.*\)"/\1/') + VERSION_CODE=$(grep 'versionCode = ' gradle/libs.versions.toml | sed 's/versionCode = "\(.*\)"/\1/') + echo "name=$VERSION" >> $GITHUB_OUTPUT + echo "code=$VERSION_CODE" >> $GITHUB_OUTPUT + echo "Building version: $VERSION (code: $VERSION_CODE)" + + - name: Set up JDK 17 + uses: actions/setup-java@v3.13.0 + with: + distribution: 'adopt' + java-version: '17' + + - name: Grant execute permissions for gradlew + run: chmod +x ./gradlew + + - name: Prepare QNN libraries + run: ./scripts/prepare_qnn_libs.sh + + - name: Decode Keystore + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + if: ${{ env.KEYSTORE_BASE64 != '' }} + run: | + mkdir -p app/keystore + echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/keystore/release.keystore + echo "keystore=keystore/release.keystore" > app/keystore/signing.properties + echo "keystore.alias=${{ secrets.KEYSTORE_ALIAS }}" >> app/keystore/signing.properties + echo "keystore.password=${{ secrets.KEYSTORE_PASSWORD }}" >> app/keystore/signing.properties + + - name: Build Full Release APK + run: ./gradlew :app:assembleFullRelease + + - name: Build FOSS Release APK + run: ./gradlew :app:assembleFossRelease + + - name: Rename APKs + run: | + VERSION="${{ steps.version.outputs.name }}" + mkdir -p release-apks + + # Full Release + if [ -f app/build/outputs/apk/full/release/app-full-release.apk ]; then + cp app/build/outputs/apk/full/release/app-full-release.apk release-apks/pdai-full-release-${VERSION}.apk + fi + + # FOSS Release + if [ -f app/build/outputs/apk/foss/release/app-foss-release.apk ]; then + cp app/build/outputs/apk/foss/release/app-foss-release.apk release-apks/pdai-foss-release-${VERSION}.apk + fi + + - name: Upload Release APKs + uses: actions/upload-artifact@v4 + with: + name: pdai-release-${{ steps.version.outputs.name }} + path: release-apks/*.apk + retention-days: 90 + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION="${{ steps.version.outputs.name }}" + TAG="v${VERSION}" + + # Create tag if it doesn't exist + git tag $TAG || true + git push origin $TAG || true + + # Create release with release notes + gh release create $TAG \ + --title "${VERSION}" \ + --notes-file RELEASE_NOTES_${VERSION}.md \ + release-apks/*.apk diff --git a/.gitignore b/.gitignore index 97794a835..0ae77eab3 100755 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,22 @@ jacoco.exec # Fastlane fastlane/report.xml .kotlin + +# QNN proprietary libraries (must be downloaded separately) +feature/qnn/src/main/jniLibs/ +feature/qnn/src/main/assets/qnnlibs/*.so +feature/qnn/src/main/assets/cvtbase/*.mnn +feature/qnn/src/main/assets/cvtbase/*.json +!feature/qnn/src/main/jniLibs/arm64-v8a/.gitkeep +!feature/qnn/src/main/assets/qnnlibs/.gitkeep +!feature/qnn/src/main/assets/cvtbase/.gitkeep + +# QNN extracted files +LocalDream.apk +qnn-extracted/ + +# Platform tools +platform-tools/ + +# Debug keystore +debug.keystore diff --git a/README.md b/README.md index b0762cb2d..2a220e360 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,10 @@ -![Header](docs/assets/github-header-image.png) +![Header](docs/assets/tlogo_256.png) -# Stable-Diffusion-Android (SDAI) +# Pocket Diffusion Android (PDAI) -![Google Play](https://img.shields.io/endpoint?color=blue&logo=google-play&logoColor=white&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Dcom.shifthackz.aisdv1.app%26l%3DGoogle%2520Play%26m%3D%24version) -![F-Droid](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ff-droid.org%2Fapi%2Fv1%2Fpackages%2Fcom.shifthackz.aisdv1.app.foss&query=%24.packages%5B0%5D.versionName&label=F-Droid&link=https%3A%2F%2Ff-droid.org%2Fpackages%2Fcom.shifthackz.aisdv1.app.foss%2F) +[![GitHub](https://img.shields.io/github/v/release/crim50n/Pocket-Diffusion-Android?label=GitHub)](https://github.com/crim50n/Pocket-Diffusion-Android/releases) - -[![Google Play](docs/assets/google_play.png)](https://play.google.com/store/apps/details?id=com.shifthackz.aisdv1.app) -[![F-Droid](docs/assets/fdroid.png)](https://f-droid.org/packages/com.shifthackz.aisdv1.app.foss) -[![4pda](docs/assets/4pda.png)](https://4pda.to/forum/index.php?showtopic=1082639) - -Stable Diffusion AI (SDAI) is an easy-to-use app that: +Pocket Diffusion (PDAI) is an easy-to-use app that: - Brings you the power of digital art creativity with Stable Diffusion AI - Gives you freedom to choose your AI generation provider @@ -29,7 +23,11 @@ Stable Diffusion AI (SDAI) is an easy-to-use app that: - Can use server environment powered by [Hugging Face Inference API](https://huggingface.co/docs/api-inference/quicktour). - Can use server environment powered by [OpenAI](https://platform.openai.com/docs/api-reference/images) (DALL-E-2, DALL-E-3). - Can use server environment powered by [Stability AI](https://platform.stability.ai/). +- Can use server environment powered by [Fal.AI](https://fal.ai/). - Can use local environment powered by LocalDiffusion (Beta) + - Microsoft ONNX Runtime (txt2img) + - Google AI MediaPipe (txt2img) + - Qualcomm QNN with NPU acceleration (txt2img, img2img) - Supports original Txt2Img, Img2Img modes - **Positive** and **negative** prompt support - Support dynamic **size** in range from 64 to 2048 px (for width and height) @@ -52,9 +50,14 @@ Stable Diffusion AI (SDAI) is an easy-to-use app that: - Textual inversion picker (for A1111) - Hypernetworks picker (for A1111) - SD Model picker (for A1111) + - Forge Modules support (for A1111/Forge) + - ADetailer (After Detailer) support for enhanced face/hand/body fixing (for A1111/Forge) + - Hires.Fix support for high-resolution upscaling (for A1111/Forge) - In-app Gallery, stored locally, contains all AI generated images - - Displays generated images grid - - Image detail view: Zoom, Pinch, Generation Info. + - Displays generated images grid with pagination + - Image detail view: Advanced zoom/pan controls, Pinch-to-zoom, Generation Info + - Navigation between images with swipe gestures + - InPaint editor with zoom/pan support for precise mask drawing - Export all gallery to **.zip** file - Export single photo to **.zip** file - Settings @@ -62,7 +65,8 @@ Stable Diffusion AI (SDAI) is an easy-to-use app that: - Active SD Model selection - Server availability monitoring (http-ping method) - Enable/Disable auto-saving of generated images - - Enable/Disable saving generated images to `Download/SDAI` android MediaStore folder + - Enable/Disable saving generated images to `Download/PDAI` android MediaStore folder + - Optimized file-based media storage (faster loading and reduced memory usage) - Clear gallery / app cache ## Setup instruction @@ -71,7 +75,7 @@ Stable Diffusion AI (SDAI) is an easy-to-use app that: This requires you to have the AUTOMATIC1111 WebUI that is running in server mode. -You can have it running either on your own hardware with modern GPU from Nvidia or AMD, or running it using Google Colab. +You can have it running either on your own hardware with modern GPU from Nvidia or AMD, or running it using Google Colab. 1. Follow the setup instructions on [Stable-Diffusion-WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) repository. 2. Add the arguments `--api --listen` to the command line arguments of WebUI launch script. @@ -90,7 +94,7 @@ Please refer to the [SwarmUI documentation](https://github.com/mcmonkeyprojects/ ### Option 3: Use AI Horde -[AI Horde](https://stablehorde.net/) is a crowdsourced distributed cluster of Image generation workers and text generation workers. +[AI Horde](https://stablehorde.net/) is a crowdsourced distributed cluster of Image generation workers and text generation workers. AI Horde requires to use API KEY, this mobile app allows to use either default API KEY (which is "0000000000"), or type your own. You can sign up and get your own AI Horde API KEY [here](https://stablehorde.net/register). @@ -102,7 +106,7 @@ Hugging Face Inference requires to use API KEY, which can be created in [Hugging ### Option 5: OpenAI -OpenAI provides a service for text to image generation using [DALLE-2](https://openai.com/dall-e-2) or [DALLE-3](https://openai.com/dall-e-3) models. This service is paid. +OpenAI provides a service for text to image generation using [DALLE-2](https://openai.com/dall-e-2) or [DALLE-3](https://openai.com/dall-e-3) models. This service is paid. OpenAI requires to use API KEY, which can be created in [OpenAI API Key settings](https://platform.openai.com/api-keys). @@ -112,19 +116,55 @@ OpenAI requires to use API KEY, which can be created in [OpenAI API Key settings StabilityAI requires to use API KEY, which can be created in [API Keys page](https://platform.stability.ai/account/keys). -### Option 7: Local Diffusion Microsoft ONNX Runtime (Beta) +### Option 7: Fal.AI + +[Fal.AI](https://fal.ai/) is a modern AI generation service with support for latest FLUX models family. + +**Built-in models:** +- FLUX.1-dev (high quality, slower) +- FLUX.1-schnell (fast generation) +- FLUX-LoRA (custom LoRA support) +- FLUX-2 (latest generation) +- FLUX-Kontext (context-aware generation) + +**Features:** +- Import OpenAPI.json from fal.ai to add new models +- Dynamic form generation based on model parameters +- Support for custom endpoints + +Fal.AI requires to use API KEY, which can be created in [Fal.AI dashboard](https://fal.ai/dashboard/keys). + +### Option 8: Local Diffusion Microsoft ONNX Runtime (Beta) Only **txt2img** mode is supported. -Allows to use phone resources to generate images. +Allows to use phone resources to generate images using Microsoft ONNX Runtime. -### Option 8: Local Diffusion Google AI MediaPipe (Beta) +### Option 9: Local Diffusion Google AI MediaPipe (Beta) Available only in **playstore** and **full** flavors. Only **txt2img** mode is supported. -Allows to use phone resources to generate images. +Allows to use phone resources to generate images using Google MediaPipe framework. + +### Option 10: Local Diffusion Qualcomm QNN (Beta) + +Available only in **playstore** and **full** flavors. + +Supports both **txt2img** and **img2img** modes. + +Uses Qualcomm QNN SDK with NPU acceleration (HTP) and MNN backend for fast on-device Stable Diffusion generation. + +**Requirements:** +- Snapdragon 8 Gen 1 or newer chipset +- Supports both NPU-accelerated models (8Gen1, 8Gen2/3/4) and CPU/MNN models + +**Features:** +- Hardware NPU acceleration for faster generation +- Multiple pre-built models available for download +- Support for custom models (scan local folders) +- Lower power consumption compared to CPU-only backends ## Supported languages @@ -144,14 +184,10 @@ Any contributions to the translations are welcome. ## Difference between build flavors (Google Play, F-Droid, GitHub releases) -There are some reasons that some of the SDAI app features can not be distributed through different sources (Google Play, F-Droid) because of rules and compliance policies. - -The difference between SDAI app flavors are described at the project wiki page [Build flavor difference](https://github.com/ShiftHackZ/Stable-Diffusion-Android/wiki/Build-flavor-difference). - -## Donate +There are some reasons that some of the PDAI app features can not be distributed through different sources (Google Play, F-Droid) because of rules and compliance policies. -This software is open source, provided with no warranty, and you are welcome to use it for free. +The difference between PDAI app flavors are described at the project wiki page [Build flavor difference](https://github.com/crim50n/Pocket-Diffusion-Android/wiki/Build-flavor-difference). -In case you find this software valuable, and you'd like to say thanks and show a little support, here is the button: +## Credits -[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/shifthackz) +This project is a fork of [Stable Diffusion Android](https://github.com/ShiftHackZ/Stable-Diffusion-Android) by [ShiftHackZ](https://github.com/ShiftHackZ), licensed under GNU AGPL v3.0. diff --git a/RELEASE_NOTES_0.7.0.md b/RELEASE_NOTES_0.7.0.md new file mode 100644 index 000000000..2d1d4f854 --- /dev/null +++ b/RELEASE_NOTES_0.7.0.md @@ -0,0 +1,148 @@ +# PDAI v0.7.0 — Release Notes + +## New Features (compared to [SDAI](https://github.com/ShiftHackZ/Stable-Diffusion-Android) v0.6.8) + +### Fal.AI — Cloud Generation with FLUX Models + +Full integration with the Fal.AI platform for cloud-based image generation. + +**Default models:** +- FLUX Schnell, FLUX Schnell Redux +- FLUX Dev, FLUX Dev Redux, FLUX Dev Image-to-Image +- FLUX 2, FLUX 2 Flash, FLUX 2 Edit, FLUX 2 Flash Edit +- FLUX Lora, FLUX Lora Image-to-Image, FLUX Lora Inpainting +- FLUX Kontext Dev +- (and other models available via custom endpoints) + +**Modes:** +- Text-to-Image, Image-to-Image, Inpainting + +**Highlights:** +- Dynamic parameter form generation based on OpenAPI specifications + +--- + +### Qualcomm QNN — Local Generation on NPU + +Image generation on Snapdragon devices using NPU and GPU. + +**Supported chipsets:** Snapdragon 8 Gen 1 and newer + +**Runtimes:** +- HTP (NPU) — optimized for neural networks +- GPU (OpenCL/Adreno) — general-purpose graphics processor +- CPU — central processor (fallback option) + +**Modes:** +- Text-to-Image, Image-to-Image + +**Resolution support:** +- NPU (HTP): 512×512 to 1024×1024 +- CPU/GPU: 256×256 to 512×512 + +**Hires.Fix (NPU only):** +- Generate at 512×512 or 768×768, upscale to larger square resolution, refine with img2img pass +- Only square resolutions support Hires.Fix (512×512 → 768/1024, 768×768 → 1024) +- Configurable target resolution, steps, and denoising strength + +--- + +### Local Diffusion Enhancements + +Improved support for all local backends (ONNX, MediaPipe, QNN): + +- **Custom models scanning** — automatic detection of user-provided models from custom directories +- **Strict model validation** — each backend validates model structure before loading +- **Prompt persistence** — last used prompt is saved and restored per backend +- **Model selection in generation UI** — switch between models directly from the generation screen + +--- + +### ADetailer and Hires.Fix for A1111/Forge + +- **ADetailer** — automatic face and hand enhancement +- **Hires.Fix** — high-resolution generation with upscaling +- **Forge Modules** — basic support for Forge modules (API endpoint) + +--- + +## UI Improvements + +### ZoomableImage +- Improved scaling and gestures +- Smoother transition animations + +### InPaint +- Enhanced scaling and canvas panning controls +- Pinch-to-zoom support + +### Gallery +- Model name displayed in image details +- Optimized swipe navigation between images +- Save selected images to device gallery (in selection mode) + +### Log Export +- Log file export function for diagnostics + +### Light Theme +- Support for light status bar in app theme + +--- + +## Optimization + +### Image File Storage +- Migration from Base64 to file-based storage +- Significantly improved performance and reduced DB size +- Added `mediaPath`, `inputMediaPath` fields in DB (migration v8) + +### Network Requests +- Requests are sent only to the active generation source + +--- + +## Technical Changes + +### Rebranding SDAI → PDAI +- Log tag: `[PDAI]` instead of `[SDAI]` +- Log file: `pdaiv1.log` instead of `sdaiv1.log` +- Model path: `/Download/PDAI/` instead of `/Download/SDAI/` +- `SdaiWorkerFactory` → `PdaiWorkerFactory` +- Documentation and website updates + +### Dependency Updates +- Compose BOM → 2025.12.01 +- Lifecycle → 2.10.0 +- Navigation → 2.9.6 +- Work → 2.11.0 +- Koin → 4.1.1 +- Retrofit → 3.0.0 +- ONNX Runtime → 1.23.2 +- Core KTX → 1.17.0 +- Material → 1.13.0 +- RxJava → 3.1.12 +- Apache String Utils → 3.20.0 +- MediaPipe → 0.10.21 +- Serialization → 1.9.0 +- Turbine → 1.2.1 +- Appcompat → 1.7.1 +- Compose Activity → 1.12.2 +- Crypto → 1.1.0 +- EXIF → 1.4.2 +- Gson → 2.13.2 +- compileSdk: 35 → 36 + +### Database Migration +- **Schema version: 7 → 9** — two sequential migrations +- **New fields:** `modelName` (v9), `mediaPath`, `inputMediaPath` (v8) +- **Automatic migration** of existing data on update + +--- + +## Fixes + +- Requests to inactive servers are no longer executed +- Light status bar works correctly +- Localization files updated (RU, TR, UK, ZH) +- Added module tests for Fal.AI and Forge, updated existing tests +- Removed donate button from settings diff --git a/RELEASE_NOTES_0.7.1.md b/RELEASE_NOTES_0.7.1.md new file mode 100644 index 000000000..081d26a01 --- /dev/null +++ b/RELEASE_NOTES_0.7.1.md @@ -0,0 +1,161 @@ +# PDAI v0.7.1 — Release Notes + +## New Features + +### Favorites (Like) + +- "Favorite" button (heart) in image detail view +- Like indicator in gallery grid (red heart in the top right corner) +- Bulk like: "Like" button in selection mode likes all selected images +- "Delete Unliked" function in gallery menu — deletes all images without a like +- Real-time sync of like status between detail view and grid +- Like status persists between sessions + +### Hide Images + +- Bulk hide: "Hide" button in selection mode hides all selected images +- Bulk unhide: tapping "Hide" when all selected are hidden — reveals them +- Real-time sync of hide status between detail view and grid +- Fallback for Android < 12: dimming with icon instead of blur + +### Image Editor + +Built-in editor for generated images: + +- Rotate left/right +- Flip horizontally and vertically +- Adjust brightness, contrast, and saturation +- Save changes to original or as a new image + +--- + +### Completely Redesigned Gallery with Improved Performance + +**Smart Loading:** +- Thumbnails load only for visible items +- File-based thumbnail loading (no Base64) — fixed OOM on fast scrolling +- BlurHash placeholders — blurred preview while thumbnail loads +- Shimmer animation for items without BlurHash +- Two-level cache for thumbnails and full images + +**Grid Management:** +- Grid size from 1 to 6 columns (previously 2-5) +- Pinch to resize thumbnails +- Draggable scrollbar for fast navigation + +**Drag Selection:** +- Long press activates selection mode +- Dragging finger selects a range of images +- Dragging back deselects +- Auto-scroll when reaching screen edges +- Smooth animations when opening images + +**Selection Mode Actions:** +- Like (heart) — like all selected (or unlike if all already liked) +- Hide (eye) — hide all selected (or unhide if all already hidden) +- Delete — delete all selected +- Save to device gallery +- Export + +**Update on Delete:** +- Gallery auto-updates when deleting images from detail view +- Gallery auto-updates after generation completes + +--- + +### UI Improvements + +**Floating Generation Indicator:** +- Global generation status widget over all screens +- Swipe left/right to temporarily hide +- Automatically appears on status change (generation start/result) +- Does not block navigation (drawer opens over widget) + +**Collapsible Header:** +- Top bar hides on scroll down +- Appears on scroll up or when reaching top of list +- Standard NestedScrollConnection pattern (like Google Photos) +- Unified height of 72dp on all screens +- Background color unified to `background` (was `surface`) +- Consistent bottom padding accounting for navigation bar across all screens (txt2img, settings) + +**Swipe Navigation:** +- Swipe between home screen tabs (HorizontalPager) +- Drawer opens only by button (not edge swipe) + +**Image Viewing:** +- Double tap to zoom/reset +- Swipe up/down to show/hide info +- Fixed artifacts when swiping between images + +**Navigation Bar:** +- Smoothly hides in fullscreen view +- Automatically appears when returning to gallery + +**Image Details:** +- "Share" button +- "Save to device gallery" button +- "Favorite" (like) button + +--- + +## Fixes + +### Image Export +- Fixed OOM when exporting many images +- Direct file copy instead of loading into memory (Base64) +- Parallel processing (4 threads) for faster export + +### "Report" Button +- Disabled for Full and FOSS builds (Play Store only) + +### Aspect Ratio +- Aspect ratio now always uses width as the base (not the longer side) + +### Fal AI +- Fixed issue with API keys containing control characters +- `requestId` field in Fal AI response is now optional (fixed crash on fast completion) + +--- + +## Visual Changes + +- Updated notification icon + +--- + +## Technical Changes + +### Dependencies + +- Added BlurHash 0.3.0 for blurred placeholders in gallery + +### Database + +- Migration v10 → v11: added `blur_hash` field to generation results table +- Migration v11 → v12: added `liked` field for favorites feature + +### New Components + +- `ImageEditor` — image editing screen +- `CollapsibleScaffold` — scaffold with collapsible header +- `DraggableScrollbar` — draggable scrollbar +- `DragSelectionState` / `DragSelectionUtils` — drag selection +- `ThumbnailGenerator` — thumbnail generator +- `ImageCacheManager` — image cache manager +- `BlurHashEncoder` / `BlurHashDecoder` — BlurHash encoding/decoding +- `GalleryItemStateEvent` — real-time sync of hide/like states +- `GetThumbnailInfoUseCase` / `GetGalleryItemsRawUseCase` — use cases for file-based thumbnail loading +- `ToggleLikeUseCase` / `DeleteAllUnlikedUseCase` — use cases for favorites feature +- `LikeItemsUseCase` / `UnlikeItemsUseCase` — use cases for bulk like/unlike operations +- `HideItemsUseCase` / `UnhideItemsUseCase` — use cases for bulk hide/unhide operations + +--- + +## Localization + +Added strings: +- Share, Edit, Save to gallery +- Rotate, Settings, Brightness, Contrast, Saturation +- Delete unliked, confirm delete unliked + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0a04367b9..58e1f5dd7 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,15 +8,22 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.app" + namespace = "dev.minios.pdaiv1.app" dependenciesInfo { includeInApk = false includeInBundle = false } + packaging { + jniLibs { + // Required to extract native executable for QNN backend + useLegacyPackaging = true + } + } + defaultConfig { - applicationId = "com.shifthackz.aisdv1.app" + applicationId = "dev.minios.pdaiv1.app" versionName = libs.versions.versionName.get() versionCode = libs.versions.versionCode.get().toInt() @@ -26,17 +33,19 @@ android { buildConfigField("String", "HORDE_AI_URL", "\"https://stablehorde.net/\"") buildConfigField("String", "OPEN_AI_URL", "\"https://api.openai.com/\"") buildConfigField("String", "STABILITY_AI_URL", "\"https://api.stability.ai/\"") + buildConfigField("String", "FAL_AI_URL", "\"https://queue.fal.run/\"") buildConfigField("String", "HORDE_AI_SIGN_UP_URL", "\"https://stablehorde.net/register\"") buildConfigField("String", "HUGGING_FACE_INFO_URL", "\"https://huggingface.co/docs/api-inference/index\"") buildConfigField("String", "OPEN_AI_INFO_URL", "\"https://platform.openai.com/api-keys\"") buildConfigField("String", "STABILITY_AI_INFO_URL", "\"https://platform.stability.ai/\"") - buildConfigField("String", "UPDATE_API_URL", "\"https://sdai.moroz.cc\"") - buildConfigField("String", "REPORT_API_URL", "\"https://sdai-report.moroz.cc\"") - buildConfigField("String", "DEMO_MODE_API_URL", "\"https://sdai.moroz.cc\"") - buildConfigField("String", "POLICY_URL", "\"https://sdai.moroz.cc/policy.html\"") - buildConfigField("String", "DONATE_URL", "\"https://www.buymeacoffee.com/shifthackz\"") - buildConfigField("String", "GITHUB_SOURCE_URL", "\"https://github.com/ShiftHackZ/Stable-Diffusion-Android\"") + buildConfigField("String", "FAL_AI_INFO_URL", "\"https://fal.ai/dashboard/keys\"") + buildConfigField("String", "UPDATE_API_URL", "\"https://pdai.minios.dev\"") + buildConfigField("String", "REPORT_API_URL", "\"https://pdai.minios.dev\"") + buildConfigField("String", "DEMO_MODE_API_URL", "\"https://pdai.minios.dev\"") + buildConfigField("String", "POLICY_URL", "\"https://pdai.minios.dev/policy.html\"") + buildConfigField("String", "DONATE_URL", "\"\"") + buildConfigField("String", "GITHUB_SOURCE_URL", "\"https://github.com/crim50n/Pocket-Diffusion-Android\"") buildConfigField("String", "SETUP_INSTRUCTIONS_URL", "\"https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki\"") buildConfigField("String", "SWARM_UI_INFO_URL", "\"https://github.com/mcmonkeyprojects/SwarmUI/tree/master/docs\"") @@ -74,6 +83,7 @@ dependencies { implementation(project(":feature:auth")) implementation(project(":feature:diffusion")) implementation(project(":feature:mediapipe")) + implementation(project(":feature:qnn")) implementation(project(":feature:work")) implementation(project(":data")) implementation(project(":demo")) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 857bdb8df..1667a1b8a 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,7 @@ errorLog(t) } - initializeKoin() - initializeLogging() - initializeCursorSize() - initializeWorkManager() - } - - /** - * Overrides the cursor size to prevent Room DB fail with big base64. - * - * Reference: https://stackoverflow.com/questions/51959944/sqliteblobtoobigexception-row-too-big-to-fit-into-cursorwindow-requiredpos-0-t - */ - @SuppressLint("DiscouragedPrivateApi") - private fun initializeCursorSize() { - try { - val field = CursorWindow::class.java.getDeclaredField("sCursorWindowSize") - field.isAccessible = true - field.set(null, 100 * 1024 * 1024) // 100 Mb - } catch (e: Exception) { - errorLog(e) - } - } - - private fun initializeKoin() = startKoin { - androidContext(this@AiStableDiffusionClientApp) - modules( - notificationModule, - demoModule, - *featureModule, - preferenceModule, - providersModule, - *domainModule, - *dataModule, - backgroundWorkModule, - networkModule, - databaseModule, - validatorsModule, - imageProcessingModule, - *presentationModule, - ) - } - - private fun initializeLogging() { - if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) - } - Timber.plant(FileLoggingTree()) - } - - private fun initializeWorkManager() { - try { - val workerFactory: SdaiWorkerFactory by inject() - val configuration = Configuration.Builder() - .setWorkerFactory(workerFactory) - .build() - - WorkManager.initialize(this, configuration) - } catch (e: Exception) { - errorLog(e) - } - } -} diff --git a/app/src/main/java/com/shifthackz/aisdv1/app/di/FeatureModule.kt b/app/src/main/java/com/shifthackz/aisdv1/app/di/FeatureModule.kt deleted file mode 100644 index a98cf95fc..000000000 --- a/app/src/main/java/com/shifthackz/aisdv1/app/di/FeatureModule.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.app.di - -import com.shifthackz.aisdv1.feature.auth.di.authModule -import com.shifthackz.aisdv1.feature.diffusion.di.diffusionModule -import com.shifthackz.aisdv1.feature.mediapipe.di.mediaPipeModule - -val featureModule = arrayOf( - authModule, - diffusionModule, - mediaPipeModule, -) diff --git a/app/src/main/java/com/shifthackz/aisdv1/app/di/PreferenceModule.kt b/app/src/main/java/com/shifthackz/aisdv1/app/di/PreferenceModule.kt deleted file mode 100644 index 3dcfa0f16..000000000 --- a/app/src/main/java/com/shifthackz/aisdv1/app/di/PreferenceModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.app.di - -import android.content.Context -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl -import com.shifthackz.aisdv1.data.preference.SessionPreferenceImpl -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.preference.SessionPreference -import org.koin.android.ext.koin.androidContext -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.module - -private const val KEY_PREFERENCE_MANAGER = "aisdv1_preference_manager" - -val preferenceModule = module { - - single { - androidContext() - .getSharedPreferences(KEY_PREFERENCE_MANAGER, Context.MODE_PRIVATE) - .let(::PreferenceManagerImpl) - } - - singleOf(::SessionPreferenceImpl) bind SessionPreference::class -} diff --git a/app/src/main/java/dev/minios/pdaiv1/app/AiStableDiffusionClientApp.kt b/app/src/main/java/dev/minios/pdaiv1/app/AiStableDiffusionClientApp.kt new file mode 100755 index 000000000..b7182eade --- /dev/null +++ b/app/src/main/java/dev/minios/pdaiv1/app/AiStableDiffusionClientApp.kt @@ -0,0 +1,97 @@ +package dev.minios.pdaiv1.app + +import android.annotation.SuppressLint +import android.app.Application +import android.database.CursorWindow +import android.os.StrictMode +import android.os.StrictMode.VmPolicy +import androidx.work.Configuration +import androidx.work.WorkManager +import dev.minios.pdaiv1.app.di.featureModule +import dev.minios.pdaiv1.app.di.preferenceModule +import dev.minios.pdaiv1.app.di.providersModule +import dev.minios.pdaiv1.core.common.log.FileLoggingTree +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.imageprocessing.di.imageProcessingModule +import dev.minios.pdaiv1.core.notification.di.notificationModule +import dev.minios.pdaiv1.core.validation.di.validatorsModule +import dev.minios.pdaiv1.data.di.dataModule +import dev.minios.pdaiv1.demo.di.demoModule +import dev.minios.pdaiv1.domain.di.domainModule +import dev.minios.pdaiv1.network.di.networkModule +import dev.minios.pdaiv1.presentation.di.presentationModule +import dev.minios.pdaiv1.storage.di.databaseModule +import dev.minios.pdaiv1.work.di.PdaiWorkerFactory +import dev.minios.pdaiv1.work.di.backgroundWorkModule +import org.koin.android.ext.android.inject +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin +import timber.log.Timber + +class AiStableDiffusionClientApp : Application() { + + override fun onCreate() { + super.onCreate() + StrictMode.setVmPolicy(VmPolicy.Builder().build()) + Thread.currentThread().setUncaughtExceptionHandler { _, t -> errorLog(t) } + initializeKoin() + initializeLogging() + initializeCursorSize() + initializeWorkManager() + } + + /** + * Overrides the cursor size to prevent Room DB fail with big base64. + * + * Reference: https://stackoverflow.com/questions/51959944/sqliteblobtoobigexception-row-too-big-to-fit-into-cursorwindow-requiredpos-0-t + */ + @SuppressLint("DiscouragedPrivateApi") + private fun initializeCursorSize() { + try { + val field = CursorWindow::class.java.getDeclaredField("sCursorWindowSize") + field.isAccessible = true + field.set(null, 100 * 1024 * 1024) // 100 Mb + } catch (e: Exception) { + errorLog(e) + } + } + + private fun initializeKoin() = startKoin { + androidContext(this@AiStableDiffusionClientApp) + modules( + notificationModule, + demoModule, + *featureModule, + preferenceModule, + providersModule, + *domainModule, + *dataModule, + backgroundWorkModule, + networkModule, + databaseModule, + validatorsModule, + imageProcessingModule, + *presentationModule, + ) + } + + private fun initializeLogging() { + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } + Timber.plant(FileLoggingTree()) + } + + private fun initializeWorkManager() { + try { + val workerFactory: PdaiWorkerFactory by inject() + val configuration = Configuration.Builder() + .setWorkerFactory(workerFactory) + .build() + + WorkManager.initialize(this, configuration) + } catch (e: Exception) { + errorLog(e) + } + } +} diff --git a/app/src/main/java/dev/minios/pdaiv1/app/di/FeatureModule.kt b/app/src/main/java/dev/minios/pdaiv1/app/di/FeatureModule.kt new file mode 100644 index 000000000..0520c8213 --- /dev/null +++ b/app/src/main/java/dev/minios/pdaiv1/app/di/FeatureModule.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.app.di + +import dev.minios.pdaiv1.feature.auth.di.authModule +import dev.minios.pdaiv1.feature.diffusion.di.diffusionModule +import dev.minios.pdaiv1.feature.mediapipe.di.mediaPipeModule +import dev.minios.pdaiv1.feature.qnn.di.qnnModule + +val featureModule = arrayOf( + authModule, + diffusionModule, + mediaPipeModule, + qnnModule, +) diff --git a/app/src/main/java/dev/minios/pdaiv1/app/di/PreferenceModule.kt b/app/src/main/java/dev/minios/pdaiv1/app/di/PreferenceModule.kt new file mode 100644 index 000000000..c426c1c19 --- /dev/null +++ b/app/src/main/java/dev/minios/pdaiv1/app/di/PreferenceModule.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.app.di + +import android.content.Context +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl +import dev.minios.pdaiv1.data.preference.SessionPreferenceImpl +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.preference.SessionPreference +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +private const val KEY_PREFERENCE_MANAGER = "aisdv1_preference_manager" + +val preferenceModule = module { + + single { + androidContext() + .getSharedPreferences(KEY_PREFERENCE_MANAGER, Context.MODE_PRIVATE) + .let(::PreferenceManagerImpl) + } + + singleOf(::SessionPreferenceImpl) bind SessionPreference::class +} diff --git a/app/src/main/java/com/shifthackz/aisdv1/app/di/ProvidersModule.kt b/app/src/main/java/dev/minios/pdaiv1/app/di/ProvidersModule.kt similarity index 79% rename from app/src/main/java/com/shifthackz/aisdv1/app/di/ProvidersModule.kt rename to app/src/main/java/dev/minios/pdaiv1/app/di/ProvidersModule.kt index 5aaa0ea2f..50e356967 100755 --- a/app/src/main/java/com/shifthackz/aisdv1/app/di/ProvidersModule.kt +++ b/app/src/main/java/dev/minios/pdaiv1/app/di/ProvidersModule.kt @@ -1,29 +1,29 @@ -package com.shifthackz.aisdv1.app.di +package dev.minios.pdaiv1.app.di import android.content.Intent -import com.shifthackz.aisdv1.app.BuildConfig -import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.appbuild.BuildVersion -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.links.LinksProvider -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationStore -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionFlag -import com.shifthackz.aisdv1.feature.diffusion.environment.DeviceNNAPIFlagProvider -import com.shifthackz.aisdv1.feature.diffusion.environment.LocalModelIdProvider -import com.shifthackz.aisdv1.network.qualifiers.ApiKeyProvider -import com.shifthackz.aisdv1.network.qualifiers.ApiUrlProvider -import com.shifthackz.aisdv1.network.qualifiers.CredentialsProvider -import com.shifthackz.aisdv1.network.qualifiers.NetworkHeaders -import com.shifthackz.aisdv1.network.qualifiers.NetworkPrefixes -import com.shifthackz.aisdv1.presentation.activity.AiStableDiffusionActivity +import dev.minios.pdaiv1.app.BuildConfig +import dev.minios.pdaiv1.core.common.appbuild.ActivityIntentProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.appbuild.BuildVersion +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.links.LinksProvider +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationStore +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionFlag +import dev.minios.pdaiv1.feature.diffusion.environment.DeviceNNAPIFlagProvider +import dev.minios.pdaiv1.feature.diffusion.environment.LocalModelIdProvider +import dev.minios.pdaiv1.network.qualifiers.ApiKeyProvider +import dev.minios.pdaiv1.network.qualifiers.ApiUrlProvider +import dev.minios.pdaiv1.network.qualifiers.CredentialsProvider +import dev.minios.pdaiv1.network.qualifiers.NetworkHeaders +import dev.minios.pdaiv1.network.qualifiers.NetworkPrefixes +import dev.minios.pdaiv1.presentation.activity.AiStableDiffusionActivity import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.schedulers.Schedulers @@ -55,6 +55,7 @@ val providersModule = module { override val huggingFaceInferenceApiUrl = BuildConfig.HUGGING_FACE_INFERENCE_URL override val openAiApiUrl: String = BuildConfig.OPEN_AI_URL override val stabilityAiApiUrl = BuildConfig.STABILITY_AI_URL + override val falAiApiUrl: String = BuildConfig.FAL_AI_URL } } @@ -83,6 +84,11 @@ val providersModule = module { NetworkHeaders.AUTHORIZATION to key } + ServerSource.FAL_AI -> { + val key = "${NetworkPrefixes.KEY} ${preference.falAiApiKey}" + NetworkHeaders.AUTHORIZATION to key + } + else -> null } } @@ -111,6 +117,7 @@ val providersModule = module { override val huggingFaceUrl: String = BuildConfig.HUGGING_FACE_INFO_URL override val openAiInfoUrl: String = BuildConfig.OPEN_AI_INFO_URL override val stabilityAiInfoUrl: String = BuildConfig.STABILITY_AI_INFO_URL + override val falAiInfoUrl: String = BuildConfig.FAL_AI_INFO_URL override val privacyPolicyUrl: String = BuildConfig.POLICY_URL override val donateUrl: String = BuildConfig.DONATE_URL override val gitHubSourceUrl: String = BuildConfig.GITHUB_SOURCE_URL diff --git a/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..46240c786 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..56cda9d48 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..d313f1eb6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..e0b6a545d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..447ead2f7 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index d0a4e1dbb..000000000 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_sd_flat.xml b/app/src/main/res/drawable/ic_sd_flat.xml deleted file mode 100644 index 3d2234f98..000000000 --- a/app/src/main/res/drawable/ic_sd_flat.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index b62628527..6d7878a37 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 49639a5af..6d7878a37 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index 149588c8f..8f7363827 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 25ef07fa3..8f7363827 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index f78717505..93764d65d 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 8afdbdcfb..93764d65d 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index a422036ba..65e9e159d 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 9ceaf5952..65e9e159d 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index dfb5b55b7..7aeed66d9 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 4a0414755..7aeed66d9 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 6b5be4e07..45585be97 100755 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -11,7 +11,7 @@ diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 0997989ef..92a75edf1 100755 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -8,10 +8,11 @@ @color/catppuccin_latte_mauve @color/catppuccin_latte_base @color/catppuccin_latte_mantle + true diff --git a/build-logic/convention/src/main/kotlin/ApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/ApplicationConventionPlugin.kt index b1edf8f3c..af8ea6763 100644 --- a/build-logic/convention/src/main/kotlin/ApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/ApplicationConventionPlugin.kt @@ -1,9 +1,9 @@ import com.android.build.api.dsl.ApplicationExtension import com.android.build.gradle.BaseExtension -import com.shifthackz.aisdv1.buildlogic.configureApplication -import com.shifthackz.aisdv1.buildlogic.configureCompose -import com.shifthackz.aisdv1.buildlogic.configureFlavors -import com.shifthackz.aisdv1.buildlogic.libs +import dev.minios.pdaiv1.buildlogic.configureApplication +import dev.minios.pdaiv1.buildlogic.configureCompose +import dev.minios.pdaiv1.buildlogic.configureFlavors +import dev.minios.pdaiv1.buildlogic.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure diff --git a/build-logic/convention/src/main/kotlin/ComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/ComposeConventionPlugin.kt index 1f5845425..a4861a5c0 100644 --- a/build-logic/convention/src/main/kotlin/ComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/ComposeConventionPlugin.kt @@ -1,7 +1,7 @@ import com.android.build.api.dsl.LibraryExtension -import com.shifthackz.aisdv1.buildlogic.configureCompose -import com.shifthackz.aisdv1.buildlogic.configureKotlinAndroid -import com.shifthackz.aisdv1.buildlogic.libs +import dev.minios.pdaiv1.buildlogic.configureCompose +import dev.minios.pdaiv1.buildlogic.configureKotlinAndroid +import dev.minios.pdaiv1.buildlogic.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure diff --git a/build-logic/convention/src/main/kotlin/FlavorsConventionPlugin.kt b/build-logic/convention/src/main/kotlin/FlavorsConventionPlugin.kt index 743465f1d..e5ebfa502 100644 --- a/build-logic/convention/src/main/kotlin/FlavorsConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/FlavorsConventionPlugin.kt @@ -1,5 +1,5 @@ import com.android.build.gradle.LibraryExtension -import com.shifthackz.aisdv1.buildlogic.configureFlavorsCommon +import dev.minios.pdaiv1.buildlogic.configureFlavorsCommon import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure diff --git a/build-logic/convention/src/main/kotlin/JacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/JacocoConventionPlugin.kt index 6e83a3fcb..e26cafe10 100644 --- a/build-logic/convention/src/main/kotlin/JacocoConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/JacocoConventionPlugin.kt @@ -1,5 +1,5 @@ import com.android.build.gradle.BaseExtension -import com.shifthackz.aisdv1.buildlogic.jacocoCodeCoverageReporting +import dev.minios.pdaiv1.buildlogic.jacocoCodeCoverageReporting import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.tasks.testing.Test diff --git a/build-logic/convention/src/main/kotlin/LibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/LibraryConventionPlugin.kt index 60616a5e3..4718a947a 100644 --- a/build-logic/convention/src/main/kotlin/LibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/LibraryConventionPlugin.kt @@ -1,6 +1,6 @@ import com.android.build.gradle.LibraryExtension -import com.shifthackz.aisdv1.buildlogic.configureKotlinAndroid -import com.shifthackz.aisdv1.buildlogic.libs +import dev.minios.pdaiv1.buildlogic.configureKotlinAndroid +import dev.minios.pdaiv1.buildlogic.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure diff --git a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Android.kt b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Android.kt similarity index 97% rename from build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Android.kt rename to build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Android.kt index 343b5456a..83a41e45d 100644 --- a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Android.kt +++ b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Android.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.buildlogic +package dev.minios.pdaiv1.buildlogic import com.android.build.api.dsl.CommonExtension import org.gradle.api.JavaVersion diff --git a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Application.kt b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Application.kt similarity index 87% rename from build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Application.kt rename to build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Application.kt index 6e1cae211..9e1a9e747 100644 --- a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Application.kt +++ b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Application.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.buildlogic +package dev.minios.pdaiv1.buildlogic import com.android.build.api.dsl.ApplicationExtension import org.gradle.api.Project diff --git a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Compose.kt b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Compose.kt similarity index 98% rename from build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Compose.kt rename to build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Compose.kt index e156327bd..86b03ccd5 100644 --- a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Compose.kt +++ b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Compose.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.buildlogic +package dev.minios.pdaiv1.buildlogic import com.android.build.api.dsl.CommonExtension import org.gradle.api.Project diff --git a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Flavors.kt b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Flavors.kt similarity index 85% rename from build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Flavors.kt rename to build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Flavors.kt index 6357b9ff4..c48d14798 100644 --- a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Flavors.kt +++ b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Flavors.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.buildlogic +package dev.minios.pdaiv1.buildlogic import com.android.build.api.dsl.CommonExtension import com.android.build.gradle.BaseExtension @@ -12,20 +12,20 @@ internal fun Project.configureFlavors( productFlavors.create("full") { dimension = "type" applicationIdSuffix = ".full" - resValue("string", "app_name", "SDAI Full") + resValue("string", "app_name", "PDAI Full") buildConfigField("String", "BUILD_FLAVOR_TYPE", "\"FULL\"") } productFlavors.create("foss") { dimension = "type" applicationIdSuffix = ".foss" - resValue("string", "app_name", "SDAI FOSS") + resValue("string", "app_name", "PDAI FOSS") buildConfigField("String", "BUILD_FLAVOR_TYPE", "\"FOSS\"") } productFlavors.create("playstore") { dimension = "type" - resValue("string", "app_name", "SDAI") + resValue("string", "app_name", "PDAI") buildConfigField("String", "BUILD_FLAVOR_TYPE", "\"GOOGLE_PLAY\"") } } diff --git a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Jacoco.kt b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Jacoco.kt similarity index 98% rename from build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Jacoco.kt rename to build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Jacoco.kt index a62184c9d..dc9758394 100644 --- a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Jacoco.kt +++ b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Jacoco.kt @@ -1,6 +1,6 @@ @file:Suppress("UNUSED_VARIABLE") -package com.shifthackz.aisdv1.buildlogic +package dev.minios.pdaiv1.buildlogic import com.android.build.gradle.BaseExtension import org.gradle.api.Project diff --git a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Project.kt b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Project.kt similarity index 87% rename from build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Project.kt rename to build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Project.kt index e95faef6f..08e12529c 100644 --- a/build-logic/convention/src/main/kotlin/com/shifthackz/aisdv1/buildlogic/Project.kt +++ b/build-logic/convention/src/main/kotlin/dev/minios/pdaiv1/buildlogic/Project.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.buildlogic +package dev.minios.pdaiv1.buildlogic import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalog diff --git a/build.gradle.kts b/build.gradle.kts index 3ddd7c71e..ac0afd447 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,4 +6,41 @@ plugins { alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.google.ksp) apply false alias(libs.plugins.androidx.room) apply false + id("com.github.ben-manes.versions") version "0.51.0" + id("nl.littlerobots.version-catalog-update") version "0.8.5" +} + +fun isNonStable(version: String): Boolean { + val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.uppercase().contains(it) } + val regex = "^[0-9,.v-]+(-r)?$".toRegex() + val isStable = stableKeyword || regex.matches(version) + return isStable.not() +} + +tasks.withType { + rejectVersionIf { + isNonStable(candidate.version) && !isNonStable(currentVersion) + } +} + +versionCatalogUpdate { + keep { + // Keep versions that should not be updated + keepUnusedVersions.set(true) + keepUnusedLibraries.set(true) + keepUnusedPlugins.set(true) + } + pin { + // Pin versions that must not be updated + versions.add("paging") + versions.add("pagingCompose") + versions.add("agp") + versions.add("kotlin") + versions.add("ksp") + versions.add("versionName") + versions.add("versionCode") + versions.add("targetSdk") + versions.add("compileSdk") + versions.add("minSdk") + } } diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index cce5a8870..ffe4470ef 100755 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.core.common" + namespace = "dev.minios.pdaiv1.core.common" } dependencies { diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/ActivityIntentProvider.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/ActivityIntentProvider.kt deleted file mode 100644 index 6cc003136..000000000 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/ActivityIntentProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.core.common.appbuild - -import android.content.Intent - -fun interface ActivityIntentProvider { - operator fun invoke(): Intent -} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/KotlinExtensions.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/KotlinExtensions.kt deleted file mode 100644 index 837f0fc6c..000000000 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/KotlinExtensions.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.core.common.extensions - -inline fun T.applyIf(predicate: Boolean, block: T.() -> Unit): T { - if (!predicate) return this - return apply(block) -} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/StringExtensions.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/StringExtensions.kt deleted file mode 100644 index 0fe94bfb0..000000000 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/StringExtensions.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.core.common.extensions - -private const val PROTOCOL_DELIMITER = "://" -private const val PROTOCOL_HOLDER = "[[_PROTOCOL_]]" - -fun String.fixUrlSlashes(): String = this - .replace(PROTOCOL_DELIMITER, PROTOCOL_HOLDER) - .replace(Regex("/{2,}"), "/") - .let { str -> - when { - str.isEmpty() -> "" - str.last() == '/' -> str.substring(0, str.lastIndex) - else -> str - } - } - .replace(PROTOCOL_HOLDER, PROTOCOL_DELIMITER) diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/UnitExtensions.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/UnitExtensions.kt deleted file mode 100644 index bc08eb7c6..000000000 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/UnitExtensions.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.shifthackz.aisdv1.core.common.extensions - -val EmptyLambda: () -> Unit = {} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/time/TimeProvider.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/time/TimeProvider.kt deleted file mode 100644 index 7564bcb1f..000000000 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/time/TimeProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.core.common.time - -import java.util.Date - -interface TimeProvider { - fun nanoTime(): Long - fun currentTimeMillis(): Long - fun currentDate(): Date -} diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/ActivityIntentProvider.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/ActivityIntentProvider.kt new file mode 100644 index 000000000..c0eb5ed16 --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/ActivityIntentProvider.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.core.common.appbuild + +import android.content.Intent + +fun interface ActivityIntentProvider { + operator fun invoke(): Intent +} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildInfoProvider.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildInfoProvider.kt similarity index 89% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildInfoProvider.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildInfoProvider.kt index cc97b3708..0fa8124c1 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildInfoProvider.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildInfoProvider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.appbuild +package dev.minios.pdaiv1.core.common.appbuild interface BuildInfoProvider { val isDebug: Boolean diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildType.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildType.kt similarity index 82% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildType.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildType.kt index 2f4c4471b..65ebf0db3 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildType.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildType.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.appbuild +package dev.minios.pdaiv1.core.common.appbuild enum class BuildType { FULL, diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildVersion.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildVersion.kt similarity index 97% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildVersion.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildVersion.kt index 27386fd0f..7c72c983b 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/appbuild/BuildVersion.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/appbuild/BuildVersion.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.appbuild +package dev.minios.pdaiv1.core.common.appbuild class BuildVersion : Comparable { private var major: Int = 0 diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/contract/RxDisposableContract.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/contract/RxDisposableContract.kt similarity index 91% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/contract/RxDisposableContract.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/contract/RxDisposableContract.kt index 63265c006..e64064738 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/contract/RxDisposableContract.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/contract/RxDisposableContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.contract +package dev.minios.pdaiv1.core.common.contract import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.Disposable diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/device/DeviceChipsetDetector.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/device/DeviceChipsetDetector.kt new file mode 100644 index 000000000..c158c531c --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/device/DeviceChipsetDetector.kt @@ -0,0 +1,105 @@ +package dev.minios.pdaiv1.core.common.device + +import android.os.Build + +/** + * Utility for detecting Qualcomm Snapdragon chipset and determining + * appropriate QNN model variant suffix. + * + * QNN models come in three variants: + * - 8gen2: For flagship chips (Snapdragon 8 Gen 2, 3, 4) + * - 8gen1: For older flagship chips (Snapdragon 8 Gen 1) + * - min: For other Snapdragon chips or unknown devices + */ +object DeviceChipsetDetector { + + /** + * Chipset model suffixes for QNN model compatibility. + * Maps SOC_MODEL to appropriate model suffix. + */ + private val chipsetModelSuffixes = mapOf( + // Snapdragon 8 Gen 1 family + "SM8450" to "8gen1", // Snapdragon 8 Gen 1 + "SM8475" to "8gen1", // Snapdragon 8+ Gen 1 + + // Snapdragon 8 Gen 2+ family (all use 8gen2 suffix) + "SM8550" to "8gen2", // Snapdragon 8 Gen 2 + "SM8550P" to "8gen2", + "QCS8550" to "8gen2", + "QCM8550" to "8gen2", + "SM8650" to "8gen2", // Snapdragon 8 Gen 3 + "SM8650P" to "8gen2", + "SM8750" to "8gen2", // Snapdragon 8 Gen 4 + "SM8750P" to "8gen2", + "SM8850" to "8gen2", // Future chips + "SM8850P" to "8gen2", + "SM8735" to "8gen2", + "SM8845" to "8gen2", + ) + + /** + * Get the device SOC model name. + * Returns empty string on older Android versions (< S). + */ + fun getDeviceSoc(): String { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Build.SOC_MODEL + } else { + "" + } + } + + /** + * Get the chipset suffix for QNN model selection. + * + * @param soc The SOC model string (usually from Build.SOC_MODEL) + * @return One of: "8gen2", "8gen1", or "min" + */ + fun getChipsetSuffix(soc: String = getDeviceSoc()): String { + // Direct match in our known chipsets + chipsetModelSuffixes[soc]?.let { return it } + + // For unknown SM* chips, default to "min" for safety + if (soc.startsWith("SM")) { + return "min" + } + + // For completely unknown devices, use min + return "min" + } + + /** + * Check if the current device has a known Qualcomm Snapdragon chipset. + */ + fun isQualcommDevice(): Boolean { + val soc = getDeviceSoc() + return soc.startsWith("SM") || soc.startsWith("QCS") || soc.startsWith("QCM") + } + + /** + * Check if the current device supports 8gen2 QNN models. + */ + fun supports8Gen2(): Boolean = getChipsetSuffix() == "8gen2" + + /** + * Check if the current device supports 8gen1 QNN models. + */ + fun supports8Gen1(): Boolean = getChipsetSuffix() == "8gen1" + + /** + * Get recommended QNN model suffix for this device. + */ + fun getRecommendedModelSuffix(): String = getChipsetSuffix() + + /** + * Get display name for the chipset suffix. + */ + fun getChipsetDisplayName(suffix: String = getChipsetSuffix()): String { + return when (suffix) { + "8gen2" -> "Snapdragon 8 Gen 2/3/4" + "8gen1" -> "Snapdragon 8 Gen 1/+" + "min" -> "Other Snapdragon" + else -> "Unknown" + } + } +} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/AppExtensions.kt similarity index 95% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/AppExtensions.kt index db9aaad04..51192f1ff 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/AppExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.extensions +package dev.minios.pdaiv1.core.common.extensions import android.app.ActivityManager import android.app.ActivityManager.RunningAppProcessInfo diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/ClipboardExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/ClipboardExtensions.kt similarity index 86% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/ClipboardExtensions.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/ClipboardExtensions.kt index 4689e0838..bef273198 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/ClipboardExtensions.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/ClipboardExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.extensions +package dev.minios.pdaiv1.core.common.extensions import android.content.ClipData import android.content.ClipboardManager diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/DateExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/DateExtensions.kt similarity index 95% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/DateExtensions.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/DateExtensions.kt index 583a353ff..a1ae4e177 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/DateExtensions.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/DateExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.extensions +package dev.minios.pdaiv1.core.common.extensions import java.text.SimpleDateFormat import java.util.Date diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/KotlinExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/KotlinExtensions.kt new file mode 100644 index 000000000..054c3d47b --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/KotlinExtensions.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.core.common.extensions + +inline fun T.applyIf(predicate: Boolean, block: T.() -> Unit): T { + if (!predicate) return this + return apply(block) +} diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/StringExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/StringExtensions.kt new file mode 100644 index 000000000..8032b86ce --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/StringExtensions.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.core.common.extensions + +private const val PROTOCOL_DELIMITER = "://" +private const val PROTOCOL_HOLDER = "[[_PROTOCOL_]]" + +fun String.fixUrlSlashes(): String = this + .replace(PROTOCOL_DELIMITER, PROTOCOL_HOLDER) + .replace(Regex("/{2,}"), "/") + .let { str -> + when { + str.isEmpty() -> "" + str.last() == '/' -> str.substring(0, str.lastIndex) + else -> str + } + } + .replace(PROTOCOL_HOLDER, PROTOCOL_DELIMITER) diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/UnitExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/UnitExtensions.kt new file mode 100644 index 000000000..b4ee18aef --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/UnitExtensions.kt @@ -0,0 +1,3 @@ +package dev.minios.pdaiv1.core.common.extensions + +val EmptyLambda: () -> Unit = {} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/UriExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/UriExtensions.kt similarity index 93% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/UriExtensions.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/UriExtensions.kt index 04f2240d7..b9135149b 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/UriExtensions.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/extensions/UriExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.extensions +package dev.minios.pdaiv1.core.common.extensions import android.content.Context import android.content.Intent diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/file/FileExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/file/FileExtensions.kt similarity index 90% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/file/FileExtensions.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/file/FileExtensions.kt index 893a57aec..213979d8b 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/file/FileExtensions.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/file/FileExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.file +package dev.minios.pdaiv1.core.common.file import android.graphics.Bitmap import java.io.* @@ -47,6 +47,9 @@ fun File.unzip() { val destinationDir = parentFile ?: return fun extractFile(inputStream: InputStream, destFilePath: String) { + val destFile = File(destFilePath) + // Create parent directories if they don't exist + destFile.parentFile?.mkdirs() val bos = BufferedOutputStream(FileOutputStream(destFilePath)) val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE) var read: Int @@ -64,7 +67,7 @@ fun File.unzip() { extractFile(inputStream, filePath) } else { val dir = File(filePath) - dir.mkdir() + dir.mkdirs() } } } diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/file/FileProviderDescriptor.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/file/FileProviderDescriptor.kt similarity index 80% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/file/FileProviderDescriptor.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/file/FileProviderDescriptor.kt index 22ce7dbba..45e907c8b 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/file/FileProviderDescriptor.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/file/FileProviderDescriptor.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.core.common.file +package dev.minios.pdaiv1.core.common.file -const val LOCAL_DIFFUSION_CUSTOM_PATH = "/storage/emulated/0/Download/SDAI/model" +const val LOCAL_DIFFUSION_CUSTOM_PATH = "/storage/emulated/0/Download/PDAI/model" interface FileProviderDescriptor { val providerPath: String diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/links/LinksProvider.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/links/LinksProvider.kt similarity index 83% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/links/LinksProvider.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/links/LinksProvider.kt index ac6670be1..925f2b943 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/links/LinksProvider.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/links/LinksProvider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.links +package dev.minios.pdaiv1.core.common.links interface LinksProvider { val hordeUrl: String @@ -6,6 +6,7 @@ interface LinksProvider { val huggingFaceUrl: String val openAiInfoUrl: String val stabilityAiInfoUrl: String + val falAiInfoUrl: String val privacyPolicyUrl: String val donateUrl: String val gitHubSourceUrl: String diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/log/FileLoggingTree.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/log/FileLoggingTree.kt similarity index 88% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/log/FileLoggingTree.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/log/FileLoggingTree.kt index 86ae92cde..927671484 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/log/FileLoggingTree.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/log/FileLoggingTree.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.core.common.log +package dev.minios.pdaiv1.core.common.log import android.util.Log -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider import org.koin.core.component.KoinComponent import org.koin.core.component.inject import timber.log.Timber @@ -80,9 +80,9 @@ class FileLoggingTree : Timber.Tree(), KoinComponent { companion object { private const val LOGGER_TIMESTAMP_FORMAT = "dd.MM.yyyy HH:mm:SS" - private const val LOGGER_DEFAULT_TAG = "[SDAI]" + private const val LOGGER_DEFAULT_TAG = "[PDAI]" - const val LOGGER_FILENAME = "sdaiv1.log" + const val LOGGER_FILENAME = "pdaiv1.log" fun clearLog(fileProviderDescriptor: FileProviderDescriptor) { val cacheDirectory = File(fileProviderDescriptor.logsCacheDirPath) diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/log/TimberLogging.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/log/TimberLogging.kt similarity index 96% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/log/TimberLogging.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/log/TimberLogging.kt index acbd48e41..f3044003f 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/log/TimberLogging.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/log/TimberLogging.kt @@ -1,6 +1,6 @@ @file:Suppress("NOTHING_TO_INLINE") -package com.shifthackz.aisdv1.core.common.log +package dev.minios.pdaiv1.core.common.log import timber.log.Timber diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/math/MathUtils.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/math/MathUtils.kt similarity index 89% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/math/MathUtils.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/math/MathUtils.kt index e318d1674..bbd535181 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/math/MathUtils.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/math/MathUtils.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.math +package dev.minios.pdaiv1.core.common.math import kotlin.math.pow import kotlin.math.roundToInt diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Heptagonal.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Heptagonal.kt new file mode 100644 index 000000000..5576b7f45 --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Heptagonal.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.core.common.model + +import java.io.Serializable + +data class Heptagonal( + val first: A, + val second: B, + val third: C, + val fourth: D, + val fifth: E, + val sixth: F, + val seventh: G, +) : Serializable { + + override fun toString(): String = "($first, $second, $third, $fourth, $fifth, $sixth, $seventh)" +} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Hexagonal.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Hexagonal.kt similarity index 86% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Hexagonal.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Hexagonal.kt index 4bc94dde4..d50ebb466 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Hexagonal.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Hexagonal.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.model +package dev.minios.pdaiv1.core.common.model import java.io.Serializable diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Octagonal.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Octagonal.kt new file mode 100644 index 000000000..605f1a9be --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Octagonal.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.core.common.model + +import java.io.Serializable + +data class Octagonal( + val first: A, + val second: B, + val third: C, + val fourth: D, + val fifth: E, + val sixth: F, + val seventh: G, + val eighth: H, +) : Serializable { + + override fun toString(): String = "($first, $second, $third, $fourth, $fifth, $sixth, $seventh, $eighth)" +} diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Quadruple.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Quadruple.kt similarity index 83% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Quadruple.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Quadruple.kt index 7234f7faf..ca55162a2 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Quadruple.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Quadruple.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.model +package dev.minios.pdaiv1.core.common.model import java.io.Serializable diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Quintuple.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Quintuple.kt similarity index 85% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Quintuple.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Quintuple.kt index e0987f1c8..facc23841 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/model/Quintuple.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/model/Quintuple.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.model +package dev.minios.pdaiv1.core.common.model import java.io.Serializable diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/reactive/RetryExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/reactive/RetryExtensions.kt similarity index 98% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/reactive/RetryExtensions.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/reactive/RetryExtensions.kt index 4159a9b44..774cc39cc 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/reactive/RetryExtensions.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/reactive/RetryExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.reactive +package dev.minios.pdaiv1.core.common.reactive import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/DispatchersProvider.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/DispatchersProvider.kt similarity index 77% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/DispatchersProvider.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/DispatchersProvider.kt index 930d56d99..d4db439c6 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/DispatchersProvider.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/DispatchersProvider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.schedulers +package dev.minios.pdaiv1.core.common.schedulers import kotlinx.coroutines.CoroutineDispatcher diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersExtensions.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersExtensions.kt similarity index 93% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersExtensions.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersExtensions.kt index 1bf09dabc..eb85edec1 100755 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersExtensions.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.schedulers +package dev.minios.pdaiv1.core.common.schedulers import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersProvider.kt similarity index 91% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersProvider.kt index 2e7bea78e..e8d73bd3e 100755 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersProvider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.schedulers +package dev.minios.pdaiv1.core.common.schedulers import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.schedulers.Schedulers diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersToken.kt similarity index 77% rename from core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt rename to core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersToken.kt index c674d7c5d..7ecfd01de 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/schedulers/SchedulersToken.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.schedulers +package dev.minios.pdaiv1.core.common.schedulers enum class SchedulersToken(val type: String) { MAIN_THREAD("Main thread"), diff --git a/core/common/src/main/java/dev/minios/pdaiv1/core/common/time/TimeProvider.kt b/core/common/src/main/java/dev/minios/pdaiv1/core/common/time/TimeProvider.kt new file mode 100644 index 000000000..929ecd1aa --- /dev/null +++ b/core/common/src/main/java/dev/minios/pdaiv1/core/common/time/TimeProvider.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.core.common.time + +import java.util.Date + +interface TimeProvider { + fun nanoTime(): Long + fun currentTimeMillis(): Long + fun currentDate(): Date +} diff --git a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/appbuild/BuildVersionTest.kt b/core/common/src/test/java/dev/minios/pdaiv1/core/common/appbuild/BuildVersionTest.kt similarity index 96% rename from core/common/src/test/java/com/shifthackz/aisdv1/core/common/appbuild/BuildVersionTest.kt rename to core/common/src/test/java/dev/minios/pdaiv1/core/common/appbuild/BuildVersionTest.kt index 7383f94ef..8cce5c63e 100644 --- a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/appbuild/BuildVersionTest.kt +++ b/core/common/src/test/java/dev/minios/pdaiv1/core/common/appbuild/BuildVersionTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.appbuild +package dev.minios.pdaiv1.core.common.appbuild import org.junit.Assert import org.junit.Test diff --git a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/DateExtensionsTest.kt b/core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/DateExtensionsTest.kt similarity index 93% rename from core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/DateExtensionsTest.kt rename to core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/DateExtensionsTest.kt index b4f77ff51..e5c740b5b 100644 --- a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/DateExtensionsTest.kt +++ b/core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/DateExtensionsTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.extensions +package dev.minios.pdaiv1.core.common.extensions import org.junit.Assert import org.junit.Test diff --git a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/KotlinExtensionsTest.kt b/core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/KotlinExtensionsTest.kt similarity index 95% rename from core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/KotlinExtensionsTest.kt rename to core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/KotlinExtensionsTest.kt index 5fb165f4b..f75a23776 100644 --- a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/KotlinExtensionsTest.kt +++ b/core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/KotlinExtensionsTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.extensions +package dev.minios.pdaiv1.core.common.extensions import org.junit.Assert import org.junit.Test diff --git a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/StringExtensionsTest.kt b/core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/StringExtensionsTest.kt similarity index 94% rename from core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/StringExtensionsTest.kt rename to core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/StringExtensionsTest.kt index 17a9c74a4..79c1d056c 100644 --- a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/extensions/StringExtensionsTest.kt +++ b/core/common/src/test/java/dev/minios/pdaiv1/core/common/extensions/StringExtensionsTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.extensions +package dev.minios.pdaiv1.core.common.extensions import org.junit.Assert import org.junit.Test diff --git a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/math/MathUtilsTest.kt b/core/common/src/test/java/dev/minios/pdaiv1/core/common/math/MathUtilsTest.kt similarity index 93% rename from core/common/src/test/java/com/shifthackz/aisdv1/core/common/math/MathUtilsTest.kt rename to core/common/src/test/java/dev/minios/pdaiv1/core/common/math/MathUtilsTest.kt index f06ceea69..50acd3343 100644 --- a/core/common/src/test/java/com/shifthackz/aisdv1/core/common/math/MathUtilsTest.kt +++ b/core/common/src/test/java/dev/minios/pdaiv1/core/common/math/MathUtilsTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.common.math +package dev.minios.pdaiv1.core.common.math import org.junit.Assert import org.junit.Test diff --git a/core/imageprocessing/build.gradle.kts b/core/imageprocessing/build.gradle.kts index 5a2f23bc9..4231c7dec 100644 --- a/core/imageprocessing/build.gradle.kts +++ b/core/imageprocessing/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.core.imageprocessing" + namespace = "dev.minios.pdaiv1.core.imageprocessing" } dependencies { @@ -11,4 +11,5 @@ dependencies { implementation(libs.koin.core) implementation(libs.koin.android) implementation(libs.rx.kotlin) + implementation(libs.blurhash) } diff --git a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/Base64EncodingConverter.kt b/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/Base64EncodingConverter.kt deleted file mode 100644 index 27542df27..000000000 --- a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/Base64EncodingConverter.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.shifthackz.aisdv1.core.imageprocessing - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.imageprocessing.Base64EncodingConverter.Input -import com.shifthackz.aisdv1.core.imageprocessing.Base64EncodingConverter.Output -import com.shifthackz.aisdv1.core.imageprocessing.contract.RxImageProcessor -import com.shifthackz.aisdv1.core.imageprocessing.utils.base64DefaultToNoWrap -import io.reactivex.rxjava3.core.Scheduler -import io.reactivex.rxjava3.core.Single - -private typealias Base64EncodingProcessor = RxImageProcessor - -class Base64EncodingConverter( - private val processingScheduler: Scheduler, -) : Base64EncodingProcessor { - - override fun invoke(input: Input): Single = Single - .create { emitter -> - convert(input).fold( - onSuccess = emitter::onSuccess, - onFailure = emitter::onError, - ) - } - .onErrorReturn { t -> - errorLog(t) - Output(input.base64) - } - .subscribeOn(processingScheduler) - - private fun convert(input: Input): Result = runCatching { - Output(base64DefaultToNoWrap(input.base64)) - } - - data class Input(val base64: String) - data class Output(val base64: String) -} diff --git a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/Base64ToBitmapConverter.kt b/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/Base64ToBitmapConverter.kt deleted file mode 100644 index 432b5ff90..000000000 --- a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/Base64ToBitmapConverter.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.shifthackz.aisdv1.core.imageprocessing - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Input -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Output -import com.shifthackz.aisdv1.core.imageprocessing.contract.RxImageProcessor -import com.shifthackz.aisdv1.core.imageprocessing.utils.base64ToBitmap -import io.reactivex.rxjava3.core.Scheduler -import io.reactivex.rxjava3.core.Single - -private typealias Base64ToBitmapProcessor = RxImageProcessor - -class Base64ToBitmapConverter( - private val processingScheduler: Scheduler, - private val fallbackBitmap: Bitmap, -) : Base64ToBitmapProcessor { - - override operator fun invoke(input: Input): Single = Single - .create { emitter -> - convert(input).fold( - onSuccess = emitter::onSuccess, - onFailure = emitter::onError, - ) - } - .onErrorReturn { t -> - errorLog(t) - Output(fallbackBitmap) - } - .subscribeOn(processingScheduler) - - private fun convert(input: Input): Result = runCatching { - Output(base64ToBitmap(input.base64ImageString)) - } - - data class Input(val base64ImageString: String) - data class Output(val bitmap: Bitmap) -} diff --git a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/BitmapToBase64Converter.kt b/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/BitmapToBase64Converter.kt deleted file mode 100644 index 73cbd2a26..000000000 --- a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/BitmapToBase64Converter.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.shifthackz.aisdv1.core.imageprocessing - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter.Input -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter.Output -import com.shifthackz.aisdv1.core.imageprocessing.contract.RxImageProcessor -import com.shifthackz.aisdv1.core.imageprocessing.utils.bitmapToBase64 -import io.reactivex.rxjava3.core.Scheduler -import io.reactivex.rxjava3.core.Single - -private typealias BitmapToBase64Processor = RxImageProcessor - -class BitmapToBase64Converter( - private val processingScheduler: Scheduler, -) : BitmapToBase64Processor { - - override operator fun invoke(input: Input): Single = Single - .create { emitter -> - convert(input).fold( - onSuccess = emitter::onSuccess, - onFailure = emitter::onError, - ) - } - .subscribeOn(processingScheduler) - - private fun convert(input: Input): Result = runCatching { - Output(bitmapToBase64(input.bitmap)) - } - - data class Input(val bitmap: Bitmap) - data class Output(val base64ImageString: String) -} diff --git a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/contract/RxImageProcessor.kt b/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/contract/RxImageProcessor.kt deleted file mode 100644 index 75791d441..000000000 --- a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/contract/RxImageProcessor.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.core.imageprocessing.contract - -import io.reactivex.rxjava3.core.Single - -interface RxImageProcessor { - operator fun invoke(input: I): Single -} diff --git a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/di/ImageProcessingModule.kt b/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/di/ImageProcessingModule.kt deleted file mode 100644 index 5608ffabf..000000000 --- a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/di/ImageProcessingModule.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.shifthackz.aisdv1.core.imageprocessing.di - -import android.graphics.BitmapFactory -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64EncodingConverter -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.core.imageprocessing.R -import org.koin.android.ext.koin.androidContext -import org.koin.dsl.module - -val imageProcessingModule = module { - - factory { - Base64ToBitmapConverter( - get().computation, - BitmapFactory.decodeResource(androidContext().resources, R.drawable.ic_broken), - ) - } - - factory { - BitmapToBase64Converter(get().computation) - } - - factory { - Base64EncodingConverter(get().computation) - } -} diff --git a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/utils/Base64ImageUtils.kt b/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/utils/Base64ImageUtils.kt deleted file mode 100644 index 855578188..000000000 --- a/core/imageprocessing/src/main/java/com/shifthackz/aisdv1/core/imageprocessing/utils/Base64ImageUtils.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.core.imageprocessing.utils - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.util.Base64 -import java.io.ByteArrayOutputStream - -fun base64ToBitmap(base64: String): Bitmap { - val imageBytes = Base64.decode(base64, Base64.DEFAULT) - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) -} - -fun bitmapToBase64(bitmap: Bitmap): String { - val outputStream = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) - return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT) -} - -fun base64DefaultToNoWrap(base64Default: String): String { - val byteArray = Base64.decode(base64Default, Base64.DEFAULT) - return Base64.encodeToString(byteArray, Base64.NO_WRAP) -} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/Base64EncodingConverter.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/Base64EncodingConverter.kt new file mode 100644 index 000000000..632bb7a0b --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/Base64EncodingConverter.kt @@ -0,0 +1,36 @@ +package dev.minios.pdaiv1.core.imageprocessing + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.imageprocessing.Base64EncodingConverter.Input +import dev.minios.pdaiv1.core.imageprocessing.Base64EncodingConverter.Output +import dev.minios.pdaiv1.core.imageprocessing.contract.RxImageProcessor +import dev.minios.pdaiv1.core.imageprocessing.utils.base64DefaultToNoWrap +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single + +private typealias Base64EncodingProcessor = RxImageProcessor + +class Base64EncodingConverter( + private val processingScheduler: Scheduler, +) : Base64EncodingProcessor { + + override fun invoke(input: Input): Single = Single + .create { emitter -> + convert(input).fold( + onSuccess = emitter::onSuccess, + onFailure = emitter::onError, + ) + } + .onErrorReturn { t -> + errorLog(t) + Output(input.base64) + } + .subscribeOn(processingScheduler) + + private fun convert(input: Input): Result = runCatching { + Output(base64DefaultToNoWrap(input.base64)) + } + + data class Input(val base64: String) + data class Output(val base64: String) +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/Base64ToBitmapConverter.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/Base64ToBitmapConverter.kt new file mode 100644 index 000000000..17499c3df --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/Base64ToBitmapConverter.kt @@ -0,0 +1,38 @@ +package dev.minios.pdaiv1.core.imageprocessing + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter.Input +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter.Output +import dev.minios.pdaiv1.core.imageprocessing.contract.RxImageProcessor +import dev.minios.pdaiv1.core.imageprocessing.utils.base64ToBitmap +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single + +private typealias Base64ToBitmapProcessor = RxImageProcessor + +class Base64ToBitmapConverter( + private val processingScheduler: Scheduler, + private val fallbackBitmap: Bitmap, +) : Base64ToBitmapProcessor { + + override operator fun invoke(input: Input): Single = Single + .create { emitter -> + convert(input).fold( + onSuccess = emitter::onSuccess, + onFailure = emitter::onError, + ) + } + .onErrorReturn { t -> + errorLog(t) + Output(fallbackBitmap) + } + .subscribeOn(processingScheduler) + + private fun convert(input: Input): Result = runCatching { + Output(base64ToBitmap(input.base64ImageString)) + } + + data class Input(val base64ImageString: String) + data class Output(val bitmap: Bitmap) +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/BitmapToBase64Converter.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/BitmapToBase64Converter.kt new file mode 100644 index 000000000..8320a2529 --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/BitmapToBase64Converter.kt @@ -0,0 +1,32 @@ +package dev.minios.pdaiv1.core.imageprocessing + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter.Input +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter.Output +import dev.minios.pdaiv1.core.imageprocessing.contract.RxImageProcessor +import dev.minios.pdaiv1.core.imageprocessing.utils.bitmapToBase64 +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single + +private typealias BitmapToBase64Processor = RxImageProcessor + +class BitmapToBase64Converter( + private val processingScheduler: Scheduler, +) : BitmapToBase64Processor { + + override operator fun invoke(input: Input): Single = Single + .create { emitter -> + convert(input).fold( + onSuccess = emitter::onSuccess, + onFailure = emitter::onError, + ) + } + .subscribeOn(processingScheduler) + + private fun convert(input: Input): Result = runCatching { + Output(bitmapToBase64(input.bitmap)) + } + + data class Input(val bitmap: Bitmap) + data class Output(val base64ImageString: String) +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/ThumbnailGenerator.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/ThumbnailGenerator.kt new file mode 100644 index 000000000..fa007f434 --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/ThumbnailGenerator.kt @@ -0,0 +1,175 @@ +package dev.minios.pdaiv1.core.imageprocessing + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import dev.minios.pdaiv1.core.imageprocessing.cache.ImageCacheManager +import dev.minios.pdaiv1.core.imageprocessing.utils.base64ToBitmap +import dev.minios.pdaiv1.core.imageprocessing.utils.base64ToThumbnailBitmap +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import java.io.File + +/** + * Generates thumbnail images from Base64 encoded images or files. + * Uses ImageCacheManager for caching. + */ +class ThumbnailGenerator( + private val processingScheduler: Scheduler, + private val imageCacheManager: ImageCacheManager, + private val fallbackBitmap: Bitmap, +) { + + /** + * Generates a thumbnail from a file path. + * First checks cache, then generates if not found. + * Uses inSampleSize decoding for memory efficiency. + */ + fun generateFromFile( + id: String, + filePath: String, + targetSize: Int = ImageCacheManager.THUMBNAIL_SIZE, + ): Single = Single + .defer { + // Check cache first + val cached = imageCacheManager.getThumbnail(id) + if (cached != null) { + return@defer Single.just(cached) + } + + // Generate thumbnail from file with subsampled decoding + Single.fromCallable { + val file = File(filePath) + if (!file.exists()) { + return@fromCallable fallbackBitmap + } + + // First decode bounds only + val options = BitmapFactory.Options().apply { + inJustDecodeBounds = true + } + BitmapFactory.decodeFile(filePath, options) + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, targetSize, targetSize) + options.inJustDecodeBounds = false + + // Decode with inSampleSize + val subsampledBitmap = BitmapFactory.decodeFile(filePath, options) + ?: return@fromCallable fallbackBitmap + + // Final scale to exact target size if needed + val thumbnail = createThumbnail(subsampledBitmap, targetSize) + + // Cache the thumbnail + imageCacheManager.putThumbnail(id, thumbnail) + + // Recycle intermediate bitmap if different from result + if (subsampledBitmap != thumbnail && !subsampledBitmap.isRecycled) { + subsampledBitmap.recycle() + } + + thumbnail + } + } + .onErrorReturnItem(fallbackBitmap) + .subscribeOn(processingScheduler) + + /** + * Generates a thumbnail from base64 string. + * First checks cache, then generates if not found. + * Uses inSampleSize decoding for memory efficiency. + */ + fun generate( + id: String, + base64ImageString: String, + targetSize: Int = ImageCacheManager.THUMBNAIL_SIZE, + ): Single = Single + .defer { + // Check cache first + val cached = imageCacheManager.getThumbnail(id) + if (cached != null) { + return@defer Single.just(cached) + } + + // Generate thumbnail with subsampled decoding + Single.fromCallable { + // Decode with inSampleSize for memory efficiency + val subsampledBitmap = base64ToThumbnailBitmap(base64ImageString, targetSize) + + // Final scale to exact target size if needed + val thumbnail = createThumbnail(subsampledBitmap, targetSize) + + // Cache the thumbnail + imageCacheManager.putThumbnail(id, thumbnail) + + // Recycle intermediate bitmap if different from result + if (subsampledBitmap != thumbnail && !subsampledBitmap.isRecycled) { + subsampledBitmap.recycle() + } + + thumbnail + } + } + .onErrorReturnItem(fallbackBitmap) + .subscribeOn(processingScheduler) + + /** + * Gets or generates a full-size image from base64. + */ + fun getFullImage( + id: String, + base64ImageString: String, + ): Single = Single + .defer { + // Check cache first + val cached = imageCacheManager.getFullImage(id) + if (cached != null) { + return@defer Single.just(cached) + } + + // Load full image + Single.fromCallable { + val bitmap = base64ToBitmap(base64ImageString) + imageCacheManager.putFullImage(id, bitmap) + bitmap + } + } + .onErrorReturnItem(fallbackBitmap) + .subscribeOn(processingScheduler) + + private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { + val (height: Int, width: Int) = options.run { outHeight to outWidth } + var inSampleSize = 1 + + if (height > reqHeight || width > reqWidth) { + val halfHeight: Int = height / 2 + val halfWidth: Int = width / 2 + + while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { + inSampleSize *= 2 + } + } + + return inSampleSize + } + + private fun createThumbnail(source: Bitmap, targetSize: Int): Bitmap { + if (source.width <= targetSize && source.height <= targetSize) { + return source + } + + val aspectRatio = source.width.toFloat() / source.height.toFloat() + val targetWidth: Int + val targetHeight: Int + + if (aspectRatio > 1) { + targetWidth = targetSize + targetHeight = (targetSize / aspectRatio).toInt() + } else { + targetHeight = targetSize + targetWidth = (targetSize * aspectRatio).toInt() + } + + return Bitmap.createScaledBitmap(source, targetWidth, targetHeight, true) + } +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/blurhash/BlurHashDecoder.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/blurhash/BlurHashDecoder.kt new file mode 100644 index 000000000..04638e1ad --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/blurhash/BlurHashDecoder.kt @@ -0,0 +1,69 @@ +package dev.minios.pdaiv1.core.imageprocessing.blurhash + +import android.graphics.Bitmap +import com.vanniktech.blurhash.BlurHash +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single + +/** + * Decodes BlurHash strings to Bitmap images for placeholders. + */ +class BlurHashDecoder( + private val processingScheduler: Scheduler, + private val fallbackBitmap: Bitmap, +) { + + /** + * Decodes a BlurHash string to a Bitmap synchronously. + * Use for inline Composable rendering. + */ + fun decodeSync( + hash: String, + width: Int = DEFAULT_SIZE, + height: Int = DEFAULT_SIZE, + ): Bitmap? { + if (hash.isBlank()) return null + return try { + BlurHash.decode(hash, width, height) + } catch (e: Exception) { + null + } + } + + /** + * Decodes a BlurHash string to a Bitmap. + * @param hash The BlurHash string to decode + * @param width Target width of the decoded bitmap + * @param height Target height of the decoded bitmap + * @return Single emitting the decoded Bitmap + */ + fun decode( + hash: String, + width: Int = DEFAULT_SIZE, + height: Int = DEFAULT_SIZE, + ): Single = Single + .fromCallable { + if (hash.isBlank()) { + return@fromCallable fallbackBitmap + } + BlurHash.decode(hash, width, height) ?: fallbackBitmap + } + .onErrorReturnItem(fallbackBitmap) + .subscribeOn(processingScheduler) + + companion object { + const val DEFAULT_SIZE = 32 // Small size for blur placeholder + + /** + * Static decode for simple use cases without dependency injection. + */ + fun decodeStatic(hash: String, width: Int = DEFAULT_SIZE, height: Int = DEFAULT_SIZE): Bitmap? { + if (hash.isBlank()) return null + return try { + BlurHash.decode(hash, width, height) + } catch (e: Exception) { + null + } + } + } +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/blurhash/BlurHashEncoder.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/blurhash/BlurHashEncoder.kt new file mode 100644 index 000000000..bd673f983 --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/blurhash/BlurHashEncoder.kt @@ -0,0 +1,47 @@ +package dev.minios.pdaiv1.core.imageprocessing.blurhash + +import android.graphics.Bitmap +import com.vanniktech.blurhash.BlurHash +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single + +/** + * Encodes Bitmap images to BlurHash strings for progressive loading placeholders. + */ +class BlurHashEncoder( + private val processingScheduler: Scheduler, +) { + + /** + * Encodes a bitmap to a BlurHash string synchronously. + * Use this for inline processing where async is not needed. + */ + fun encodeSync( + bitmap: Bitmap, + componentX: Int = DEFAULT_COMPONENT_X, + componentY: Int = DEFAULT_COMPONENT_Y, + ): String = BlurHash.encode(bitmap, componentX, componentY) ?: "" + + /** + * Encodes a bitmap to a BlurHash string. + * @param bitmap The source bitmap to encode + * @param componentX Horizontal components (1-9), higher = more detail + * @param componentY Vertical components (1-9), higher = more detail + * @return Single emitting the BlurHash string + */ + fun encode( + bitmap: Bitmap, + componentX: Int = DEFAULT_COMPONENT_X, + componentY: Int = DEFAULT_COMPONENT_Y, + ): Single = Single + .fromCallable { + BlurHash.encode(bitmap, componentX, componentY) + ?: throw IllegalStateException("Failed to encode BlurHash") + } + .subscribeOn(processingScheduler) + + companion object { + const val DEFAULT_COMPONENT_X = 4 + const val DEFAULT_COMPONENT_Y = 3 + } +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/cache/ImageCacheManager.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/cache/ImageCacheManager.kt new file mode 100644 index 000000000..8423578fe --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/cache/ImageCacheManager.kt @@ -0,0 +1,71 @@ +package dev.minios.pdaiv1.core.imageprocessing.cache + +import android.graphics.Bitmap +import android.util.LruCache + +/** + * Manages dual-layer image caching for gallery: + * - Thumbnail cache: stores small preview images (256x256) + * - Full image cache: stores full resolution images for viewing + */ +class ImageCacheManager( + thumbnailCacheSize: Int = DEFAULT_THUMBNAIL_CACHE_SIZE, + fullImageCacheSize: Int = DEFAULT_FULL_IMAGE_CACHE_SIZE, +) { + + private val thumbnailCache = object : LruCache(thumbnailCacheSize) { + override fun sizeOf(key: String, bitmap: Bitmap): Int { + return bitmap.byteCount / 1024 // Size in KB + } + } + + private val fullImageCache = object : LruCache(fullImageCacheSize) { + override fun sizeOf(key: String, bitmap: Bitmap): Int { + return bitmap.byteCount / 1024 // Size in KB + } + } + + fun getThumbnail(id: String): Bitmap? = thumbnailCache.get(id) + + fun putThumbnail(id: String, bitmap: Bitmap) { + thumbnailCache.put(id, bitmap) + } + + fun getFullImage(id: String): Bitmap? = fullImageCache.get(id) + + fun putFullImage(id: String, bitmap: Bitmap) { + fullImageCache.put(id, bitmap) + } + + fun removeThumbnail(id: String) { + thumbnailCache.remove(id) + } + + fun removeFullImage(id: String) { + fullImageCache.remove(id) + } + + fun clearThumbnails() { + thumbnailCache.evictAll() + } + + fun clearFullImages() { + fullImageCache.evictAll() + } + + fun clear() { + clearThumbnails() + clearFullImages() + } + + companion object { + const val THUMBNAIL_SIZE = 256 // px + + // Cache size in KB (about 2500 thumbnails of ~50KB each = ~125MB) + // Increased for smoother gallery scrolling like Immich + private const val DEFAULT_THUMBNAIL_CACHE_SIZE = 125_000 + + // Cache size in KB (about 10 full images of ~5MB each = ~50MB) + private const val DEFAULT_FULL_IMAGE_CACHE_SIZE = 50_000 + } +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/contract/RxImageProcessor.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/contract/RxImageProcessor.kt new file mode 100644 index 000000000..f1eae3dbf --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/contract/RxImageProcessor.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.core.imageprocessing.contract + +import io.reactivex.rxjava3.core.Single + +interface RxImageProcessor { + operator fun invoke(input: I): Single +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/di/ImageProcessingModule.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/di/ImageProcessingModule.kt new file mode 100644 index 000000000..584e49abf --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/di/ImageProcessingModule.kt @@ -0,0 +1,57 @@ +package dev.minios.pdaiv1.core.imageprocessing.di + +import android.graphics.BitmapFactory +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64EncodingConverter +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.imageprocessing.R +import dev.minios.pdaiv1.core.imageprocessing.ThumbnailGenerator +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashDecoder +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.core.imageprocessing.cache.ImageCacheManager +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val imageProcessingModule = module { + + single { + ImageCacheManager() + } + + factory { + Base64ToBitmapConverter( + get().computation, + BitmapFactory.decodeResource(androidContext().resources, R.drawable.ic_broken), + ) + } + + factory { + ThumbnailGenerator( + processingScheduler = get().computation, + imageCacheManager = get(), + fallbackBitmap = BitmapFactory.decodeResource(androidContext().resources, R.drawable.ic_broken), + ) + } + + factory { + BitmapToBase64Converter(get().computation) + } + + factory { + Base64EncodingConverter(get().computation) + } + + factory { + BlurHashEncoder( + processingScheduler = get().computation, + ) + } + + factory { + BlurHashDecoder( + processingScheduler = get().computation, + fallbackBitmap = BitmapFactory.decodeResource(androidContext().resources, R.drawable.ic_broken), + ) + } +} diff --git a/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/utils/Base64ImageUtils.kt b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/utils/Base64ImageUtils.kt new file mode 100644 index 000000000..bfba980a3 --- /dev/null +++ b/core/imageprocessing/src/main/java/dev/minios/pdaiv1/core/imageprocessing/utils/Base64ImageUtils.kt @@ -0,0 +1,67 @@ +package dev.minios.pdaiv1.core.imageprocessing.utils + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 +import java.io.ByteArrayOutputStream + +fun base64ToBitmap(base64: String): Bitmap { + val imageBytes = Base64.decode(base64, Base64.DEFAULT) + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) +} + +/** + * Decodes base64 to bitmap with subsampling for memory efficiency. + * Uses inSampleSize to decode smaller image directly, avoiding + * full-size decode + scale overhead. + * + * @param base64 The base64 encoded image string + * @param targetSize The target size (width/height) for the thumbnail + * @return Subsampled bitmap fitting within targetSize + */ +fun base64ToThumbnailBitmap(base64: String, targetSize: Int): Bitmap { + val imageBytes = Base64.decode(base64, Base64.DEFAULT) + + // First pass: decode bounds only (no memory allocation for pixels) + val options = BitmapFactory.Options().apply { + inJustDecodeBounds = true + } + BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) + + // Calculate inSampleSize for efficient decoding + options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, targetSize) + options.inJustDecodeBounds = false + + // Second pass: decode with subsampling + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) + ?: throw IllegalArgumentException("Failed to decode bitmap") +} + +/** + * Calculates optimal inSampleSize for decoding. + * inSampleSize must be power of 2 for best performance. + */ +private fun calculateInSampleSize(width: Int, height: Int, targetSize: Int): Int { + var inSampleSize = 1 + if (width > targetSize || height > targetSize) { + val halfWidth = width / 2 + val halfHeight = height / 2 + // Calculate largest inSampleSize that keeps both dimensions >= targetSize + while ((halfWidth / inSampleSize) >= targetSize && + (halfHeight / inSampleSize) >= targetSize) { + inSampleSize *= 2 + } + } + return inSampleSize +} + +fun bitmapToBase64(bitmap: Bitmap): String { + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT) +} + +fun base64DefaultToNoWrap(base64Default: String): String { + val byteArray = Base64.decode(base64Default, Base64.DEFAULT) + return Base64.encodeToString(byteArray, Base64.NO_WRAP) +} diff --git a/core/localization/build.gradle.kts b/core/localization/build.gradle.kts index bc03000a6..7eb6a114b 100644 --- a/core/localization/build.gradle.kts +++ b/core/localization/build.gradle.kts @@ -3,5 +3,5 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.core.localization" + namespace = "dev.minios.pdaiv1.core.localization" } diff --git a/core/localization/src/main/java/com/shifthackz/aisdv1/core/localization/Localization.kt b/core/localization/src/main/java/dev/minios/pdaiv1/core/localization/Localization.kt similarity index 90% rename from core/localization/src/main/java/com/shifthackz/aisdv1/core/localization/Localization.kt rename to core/localization/src/main/java/dev/minios/pdaiv1/core/localization/Localization.kt index 70911117e..11314bd47 100644 --- a/core/localization/src/main/java/com/shifthackz/aisdv1/core/localization/Localization.kt +++ b/core/localization/src/main/java/dev/minios/pdaiv1/core/localization/Localization.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.localization +package dev.minios.pdaiv1.core.localization object Localization { diff --git a/core/localization/src/main/java/com/shifthackz/aisdv1/core/localization/formatter/DurationFormatter.kt b/core/localization/src/main/java/dev/minios/pdaiv1/core/localization/formatter/DurationFormatter.kt similarity index 88% rename from core/localization/src/main/java/com/shifthackz/aisdv1/core/localization/formatter/DurationFormatter.kt rename to core/localization/src/main/java/dev/minios/pdaiv1/core/localization/formatter/DurationFormatter.kt index 0697b56af..f25bddfd7 100644 --- a/core/localization/src/main/java/com/shifthackz/aisdv1/core/localization/formatter/DurationFormatter.kt +++ b/core/localization/src/main/java/dev/minios/pdaiv1/core/localization/formatter/DurationFormatter.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.localization.formatter +package dev.minios.pdaiv1.core.localization.formatter import java.util.Locale import kotlin.math.abs diff --git a/core/localization/src/main/res/values-ru/strings.xml b/core/localization/src/main/res/values-ru/strings.xml index 61871dd5e..63865db47 100644 --- a/core/localization/src/main/res/values-ru/strings.xml +++ b/core/localization/src/main/res/values-ru/strings.xml @@ -9,9 +9,19 @@ Отмена Повторить Удалить + Поделиться + Сохранить в галерею + Редактировать + Повернуть + Настройки + Яркость + Контраст + Насыщенность Загрузить - Длина + Ширина Высота + Поменять местами + Соотношение сторон Просмотр Применить Закрыть @@ -19,10 +29,12 @@ ✅ Успешно ❌ Ошибка + Один Два Три Четыре Пять + Шесть Версия %1$s @@ -31,8 +43,8 @@ Запуск приложения! Не доступно - Режим «Изображение в изображении» (img2img) недоступен при использовании локальных ресурсов телефона для создания изображений. - Вы можете перейти к генерации на основе сервера в настройках, чтобы использовать этот режим. + Режим «Изображение в изображении» (img2img) не поддерживается бэкендами ONNX и MediaPipe. + Вы можете переключиться на QNN, серверную генерацию или другой провайдер в настройках конфигурации. Режим «Изображение в изображении» (img2img) пока не поддерживается DALL·E. Вы можете перейти к генерации на основе другого сервера в настройках, чтобы использовать этот режим. @@ -43,6 +55,7 @@ Выбрать Подключиться Настроить + Запустить Поделиться запросом Использовать в txt2img Использовать в img2img @@ -71,7 +84,22 @@ Генерация без интернета Собственный сервер CFG Шкала: %1$s + Distilled CFG Scale: %1$s + Тип модели + VAE / Text Encoder Метод выборки + Планировщик + ADetailer + Включить ADetailer + Модель детекции + Порог уверенности: %1$s + Шумоподавление: %1$s + Hires. Fix + Включить Hires. Fix + Апскейлер + Масштаб: %1$s + Шаги: %1$s (0 = как в первом проходе) + Шумоподавление: %1$s Стиль Клиппинг пресет Восстановление лиц @@ -130,7 +158,13 @@ О Stability AI Stability AI движок - Укажите свйой URL-адрес Swarm UI + Подключиться к Fal AI + Fal AI — быстрая serverless платформа для запуска FLUX и других моделей. + О Fal AI + Fal AI модель + Fal AI эндпоинт + + Укажите свой URL-адрес Swarm UI Модульный веб-интерфейс Stable Diffusion, в котором особое внимание уделяется обеспечению легкого доступа к инструментам, высокой производительности и расширяемости. Эта конфигурация использует Microsoft ONNX и позволяет запускать генерации Stable Diffusion на вашем телефоне без необходимости подключаться к удаленному серверу/облаку. @@ -138,6 +172,15 @@ Эта конфигурация использует Google AI MediaPipe и позволяет запускать генерации Stable Diffusion на вашем телефоне без необходимости подключаться к удаленному серверу/облаку. + Эта конфигурация использует Qualcomm QNN SDK с ускорением NPU (HTP) и MNN backend для быстрой генерации Stable Diffusion на устройстве. Требуется Snapdragon 8 Gen 1 или новее. + + Hires. Fix (NPU) + Включить Hires. Fix + Целевое разрешение + Шаги: %1$s (0 = как в первом проходе) + Шумоподавление: %1$s + Увеличить и улучшить детали через img2img + Веб Txt2Img @@ -148,6 +191,9 @@ Изображение Оригинал Подробности + Изображение сохранено в галерею + Все изображения сохранены в галерею + Выбранные изображения сохранены в галерею Текст в Картинку Картинка в Картинку @@ -165,7 +211,7 @@ Редактор тега Выберите источник - %1$s генераций находится в Download/SDAI + %1$s генераций находится в Download/PDAI Создано Тип Запрос @@ -183,7 +229,9 @@ Отменить выбор Выбранно: %1$s Удалить все + Удалить неотмеченные Экспортировать все + Сохранить все на устройство Режим выбора Параметры AI @@ -238,6 +286,9 @@ Экспорт галереи Эта функция экспортирует все изображения галереи в архив *.zip. Этот процесс может длиться долго, если у вас много изображений. Желаете продолжить? Эта функция экспортирует выбранные изображения галереи в архив *.zip. Желаете продолжить? + Сохранить в галерею устройства + Это сохранит все изображения в галерею вашего устройства. Процесс может занять некоторое время, если у вас много изображений. Желаете продолжить? + Это сохранит выбранные изображения в галерею вашего устройства. Желаете продолжить? Это приведет к сбросу настроек программы и удалению всех созданных изображений. Вы хотите продолжить? Предупреждение @@ -255,6 +306,9 @@ Удалить модель Вы уверены, что хотите удалить модель "%1$s"? + Удалить неотмеченные изображения + Вы уверены, что хотите безвозвратно удалить все изображения, которые не отмечены как понравившиеся? + Очистить зарисовки Вы уверены что хотите очистить зарисовки? @@ -282,6 +336,7 @@ Вы не можете использовать localhost (127.0.0.1) URL-адрес для подключения к серверу. Минимальный размер %1$s Максимальный размер %1$s + Неподдерживаемое разрешение для QNN. Используйте выпадающий список для выбора корректного разрешения. Ошибка при загрузке При обработке вашего запроса произошла ошибка. Повторите попытку позже. @@ -300,7 +355,7 @@ Загрузка кастом модели Разрешения - Чтобы иметь возможность загружать пользовательскую модель, вам необходимо разрешить приложению SDAI управлять разрешениями на хранилище, поскольку, начиная с Android 11, оно необходимо для доступа к файлам хранилища без области действия. + Чтобы иметь возможность загружать пользовательскую модель, вам необходимо разрешить приложению PDAI управлять разрешениями на хранилище, поскольку, начиная с Android 11, оно необходимо для доступа к файлам хранилища без области действия. Настроить доступ Путь к модели Путь к папке локальной модели @@ -308,6 +363,10 @@ Чтобы использовать локальную пользовательскую модель, поместите ее в локальную папку в памяти телефона. Окончательная структура папок должна быть такой: + Требуемая структура папки QNN: + Сканировать модели + Модели не найдены в указанной папке.\n\nУбедитесь, что каждая модель находится в отдельной подпапке с необходимыми файлами. + Найдено %1$d моделей: Отладка QA тест-кейсы @@ -343,7 +402,7 @@ Только маска Спасибо этим замечательным людям за поддержку ❤ - SDAI — это приложение для Android, которое: + PDAI — это приложение для Android, которое: • Предоставляет Вам возможность создавать изображения с помощью Stable Diffusion;\n• Предоставляет Вам свободу выбора провайдера генерации;\n• Нет рекламы, телеметрии, не шпионит за Вами;\n• ПО с открытым исходным кодом;\n• Вы можете использовать его бесплатно. Если программа стала Вам полезной и Вы хотите выразить благодарность и оказать небольшую поддержку, вот кнопка ниже. @@ -361,7 +420,7 @@ Расширенные функции\n[Stable Diffusion]. [Офлайн] генерация\nLocal Diffusion. Настройте приложение,\nсделайте его [своим]! - [Свобода] выбора\nпровайедра генерации. + [Свобода] выбора\nпровайдера генерации. Пожаловаться Отправить жалобу diff --git a/core/localization/src/main/res/values-tr/strings.xml b/core/localization/src/main/res/values-tr/strings.xml index 5026c1151..b75ae6327 100644 --- a/core/localization/src/main/res/values-tr/strings.xml +++ b/core/localization/src/main/res/values-tr/strings.xml @@ -7,22 +7,32 @@ Evet Hayır İptal - yeniden dene + Yeniden dene Silmek + Paylaş + Galeriye kaydet + Düzenle + Döndür + Ayarla + Parlaklık + Kontrast + Doygunluk İndirmek Genişlik Yükseklik Araştır Uygula - Kapalı + Kapat Sonraki ✅ Başarılı ❌ Başarısızlık + Bir İki Üç Dört Beş + Altı Versiyon %1$s @@ -31,8 +41,8 @@ Uygulama Başlatılıyor! Müsait değil - Görüntü oluşturmak için yerel telefon kaynaklarınızı kullanıyorsanız, Görüntüden Görüntüye modu kullanılamaz. - Image to Image modunu kullanmak için Yapılandırma ayarlarında sunucu tabanlı oluşturmaya geçebilirsiniz. + Görüntüden Görüntüye modu ONNX ve MediaPipe arka uçları tarafından desteklenmiyor. + QNN, sunucu tabanlı oluşturma veya başka bir sağlayıcıya geçiş yaparak bu modu kullanabilirsiniz. Görüntüden Görüntüye modu henüz DALL·E tarafından desteklenmiyor. Görüntüden Görüntüye modunu kullanmak için Yapılandırma ayarlarında herhangi bir başka sunucu tabanlı nesle geçebilirsiniz. @@ -43,6 +53,7 @@ Seç Bağlan Kurmak + Başlat İstemi paylaş txt2img\'de kullanın img2img\'de kullanın @@ -68,10 +79,25 @@ Grup: %1$s Toplu üretim Çoklu Modeller - Çevrimdışı nesil + Çevrimdışı oluşturma Kendi Sunucumuz CFG Scale: %1$s + Distilled CFG Scale: %1$s + Model türü + VAE / Text Encoder Örneklerme Yöntemi + Zamanlayıcı + ADetailer + ADetailer\'ı Etkinleştir + Algılama modeli + Güven eşiği: %1$s + Gürültü giderme: %1$s + Hires. Fix + Hires. Fix\'i Etkinleştir + Yükseltici + Ölçek: %1$s + Adımlar: %1$s (0 = aynı) + Gürültü giderme: %1$s Stil ön ayarı Klip yönlendirme ön ayarı Yüzleri Düzelt @@ -130,13 +156,28 @@ Hakkında Stability AI Stability AI motoru + Fal AI\'ya bağlanın + Fal AI, FLUX ve diğer modelleri çalıştırmak için hızlı bir serverless platformdur. + Hakkında Fal AI + Fal AI modeli + Fal AI endpoint + Swarm UI URL\'nizi sağlayın Araçlara kolay erişim, yüksek performans ve genişletilebilirlik üzerine odaklanan Modüler, Kararlı Yaygın Web Kullanıcı Arayüzü. - Bu yapılandırma Microsoft ONNX çalışma zamanını kullanır ve uzak bir sunucuya/buluta bağlanmaya gerek kalmadan telefonunuzda Stable Diffusion AI nesillerini çalıştırmanıza olanak tanır. + Bu yapılandırma Microsoft ONNX çalışma zamanını kullanır ve uzak bir sunucuya/buluta bağlanmaya gerek kalmadan telefonunuzda Pocket Diffusion nesillerini çalıştırmanıza olanak tanır. Uyarı! Yerel Yayılma işlevi beta testindedir. Yerel modu kullanarak yüksek kaliteli görüntüler beklemeyin. \n\nBu uygulama, güçlü olmayan telefonlarda iyi çalışmayabilir. Oluşturma performansı ve hızı, telefonunuzun kaynaklarına (CPU, RAM) ve oluşturulan görüntünün boyutuna bağlıdır (görüntü boyutu ne kadar küçükse, oluşturma o kadar hızlı olur). - Bu yapılandırma Google AI MediaPipe çalışma zamanını kullanır ve uzak bir sunucuya/buluta bağlanmaya gerek kalmadan telefonunuzda Stable Diffusion AI nesillerini çalıştırmanıza olanak tanır. + Bu yapılandırma Google AI MediaPipe çalışma zamanını kullanır ve uzak bir sunucuya/buluta bağlanmaya gerek kalmadan telefonunuzda Pocket Diffusion nesillerini çalıştırmanıza olanak tanır. + + Bu yapılandırma, cihaz üzerinde hızlı Stable Diffusion üretimi için NPU hızlandırması (HTP) ve MNN arka ucu ile Qualcomm QNN SDK kullanır. Snapdragon 8 Gen 1 veya daha yenisi gerektirir. + + Hires. Fix (NPU) + Hires. Fix\'i etkinleştir + Hedef çözünürlük + Adımlar: %1$s (0 = aynı) + Gürültü Azaltma: %1$s + Büyüt ve img2img ile iyileştir Web arayüzü @@ -165,7 +206,7 @@ Etiketi düzenle Kaynağı seçin - Kayıtlı %1$s fotoğrafınız var Download/SDAI + Kayıtlı %1$s fotoğrafınız var Download/PDAI Oluşturulma Tür İstem @@ -183,8 +224,13 @@ Tümünü seç Seçili resimler: %1$s Tümünü sil + Beğenilmeyenleri sil Tümünü dışa aktar + Tümünü cihaza kaydet Seçim modu + Resim galeriye kaydedildi + Tüm resimler galeriye kaydedildi + Seçili resimler galeriye kaydedildi AI ayarları Uygulama ayarları @@ -238,6 +284,9 @@ Galeriyi Dışa Aktar Bu işlem bütün galerideki resimleri tek bir .zip arşivi dosyası olarak dışa aktaracaktır. Galerinizin boyutuna göre bu işlem uzun bir zaman alabailir. Devam etmek istiyor musunuz? Bu, seçili galeri resimlerini *.zip arşivi olarak dışa aktaracaktır. Devam etmek istiyor musunuz? + Cihaz Galerisine Kaydet + Bu, tüm resimleri cihazınızın galerisine kaydedecektir. Çok sayıda resminiz varsa bu işlem biraz zaman alabilir. Devam etmek istiyor musunuz? + Bu, seçili resimleri cihazınızın galerisine kaydedecektir. Devam etmek istiyor musunuz? Bu işlem bütün uygulama ayarlarını ve oluşturulan resimleri silecektir. Devam etmek istiyor musunuz? Uyarı @@ -255,6 +304,9 @@ Modeli sil Modeli silmek istediğinizden emin misiniz "%1$s"? + Beğenilmeyen resimleri sil + Beğenilmemiş olarak işaretlenen tüm resimleri kalıcı olarak silmek istediğinizden emin misiniz? + Çizimleri temizle Çizimleri temizlemek istediğinizden emin misiniz? @@ -282,6 +334,7 @@ Sunucuya bağlanmak için localhost (127.0.0.1) URL adresi kullanamazsınız. Asgari boyut %1$s Azami boyut %1$s + QNN için desteklenmeyen çözünürlük. Geçerli bir çözünürlük seçmek için açılır listeyi kullanın. Yükleme başarısız İsteğiniz işlenirken bir sorun oluştu. Lütfen daha sonra tekrar deneyin. @@ -299,12 +352,16 @@ Uygulamayı açmak için buraya tıklayın. Özel modeli yükle - Özel modeli yükleyebilmek için SDAI uygulamasının depolama izinlerini yönetmesine izin vermeniz gerekir; çünkü Android 11\'den itibaren kapsamlı olmayan depolama dosyalarına erişmek gerekir. + Özel modeli yükleyebilmek için PDAI uygulamasının depolama izinlerini yönetmesine izin vermeniz gerekir; çünkü Android 11\'den itibaren kapsamlı olmayan depolama dosyalarına erişmek gerekir. Kurulum izni Yerel özel modeli kullanmak için telefonunuzun depolama alanındaki yerel klasöre yerleştirin. İzinler Son klasör yapısı şu şekilde olmalıdır:: + Gerekli QNN klasör yapısı: + Modelleri tara + Belirtilen klasörde model bulunamadı.\n\nHer modelin gerekli dosyalarla kendi alt klasöründe olduğundan emin olun. + %1$d model bulundu: Model yolu Yerel model klasör yolu Klasör seç @@ -343,8 +400,8 @@ Sadece maskeli Destekleri için bu harika insanlara teşekkürler ❤ - SDAI, şu özelliklere sahip bir Android uygulamasıdır: - • Stable Diffusion AI ile görüntü oluşturma gücü sunar;\n• Üretim sağlayıcınızı seçme özgürlüğü verir;\n• AD, telemetri içermez ve sizi gözetlemez;\n• Açık kaynaklı bir yazılımdır;\n• Ücretsiz olarak kullanabilirsiniz. + PDAI, şu özelliklere sahip bir Android uygulamasıdır: + • Pocket Diffusion ile görüntü oluşturma gücü sunar;\n• Üretim sağlayıcınızı seçme özgürlüğü verir;\n• AD, telemetri içermez ve sizi gözetlemez;\n• Açık kaynaklı bir yazılımdır;\n• Ücretsiz olarak kullanabilirsiniz. Bu yazılımı değerli bulursanız ve teşekkür etmek ve biraz destek göstermek isterseniz, aşağıdaki düğmeyi kullanabilirsiniz. Depolamak diff --git a/core/localization/src/main/res/values-uk/strings.xml b/core/localization/src/main/res/values-uk/strings.xml index 07b329dac..cb4dc59d7 100644 --- a/core/localization/src/main/res/values-uk/strings.xml +++ b/core/localization/src/main/res/values-uk/strings.xml @@ -9,8 +9,16 @@ Скасувати Повторити Видалити + Поділитися + Зберегти в галерею + Редагувати + Повернути + Налаштування + Яскравість + Контраст + Насиченість Завантажити - Довжина + Ширина Висота Дивитися Застосувати @@ -19,10 +27,12 @@ ✅ Успіх ❌ Помилка + Один Два Три Чотири П\'ять + Шість Версія %1$s @@ -31,8 +41,8 @@ Запускаємо додаток! Не доступно - Режим «Зображення в зображення» (img2img) недоступний, якщо ви використовуєте локальні ресурси телефону для створення зображень. - Ви можете перейти до генерації на основі сервера в налаштуваннях, щоб використовувати цей режим. + Режим «Зображення в зображення» (img2img) не підтримується бекендами ONNX та MediaPipe. + Ви можете переключитися на QNN, серверну генерацію або інший провайдер у налаштуваннях конфігурації. Режим «Зображення в зображення» (img2img) поки не підтримується DALL·E. Ви можете перейти до генерації на основі будь-якого іншого сервера в налаштуваннях, щоб використовувати цей режим. @@ -43,6 +53,7 @@ Обрати Підключитися Налаштувати + Запустити Поділитися запитом Відправити в txt2img Відправити в img2img @@ -71,7 +82,22 @@ Генерація без інтернету Власний сервер CFG Шкала: %1$s + Distilled CFG Scale: %1$s + Тип моделі + VAE / Text Encoder Метод вибірки + Планувальник + ADetailer + Увімкнути ADetailer + Модель детекції + Поріг впевненості: %1$s + Шумозаглушення: %1$s + Hires. Fix + Увімкнути Hires. Fix + Апскейлер + Масштаб: %1$s + Кроки: %1$s (0 = як у першому проході) + Шумозаглушення: %1$s Стиль Кліпінг пресет Відновлення обличч @@ -91,7 +117,7 @@ Тег Значення Блюр маски: %1$s - Відтупи (тільки маска): %1$s px + Відступи (тільки маска): %1$s px Режим маски Контент маски Область Inpaint @@ -116,7 +142,7 @@ Про Horde AI Отримати свій API Ключ - Підключитися доHugging Face + Підключитися до Hugging Face Hugging Face дозволяє тестувати та оцінювати загальнодоступні моделі машинного навчання або ваші власні приватні моделі. Про Hugging Face Hugging Face модель @@ -130,7 +156,13 @@ Про Stability AI Stability AI двигун - Provide your Swarm UI URL + Підключитися до Fal AI + Fal AI — швидка serverless платформа для запуску FLUX та інших моделей. + Про Fal AI + Fal AI модель + Fal AI ендпоінт + + Вкажіть URL-адресу Swarm UI Модульний веб-інтерфейс Stable Diffusion з наголосом на полегшення доступу до інструментів, високу продуктивність і розширюваність. Ця конфігурація використовує Microsoft ONNX та дозволяє запускати генерації Stable Diffusion на вашому телефоні без необхідності підключатися до віддаленого сервера/хмари. @@ -138,6 +170,15 @@ Ця конфігурація використовує Google AI MediaPipe та дозволяє запускати генерації Stable Diffusion на вашому телефоні без необхідності підключатися до віддаленого сервера/хмари. + Ця конфігурація використовує Qualcomm QNN SDK з прискоренням NPU (HTP) та MNN backend для швидкої генерації Stable Diffusion на пристрої. Потрібен Snapdragon 8 Gen 1 або новіший. + + Hires. Fix (NPU) + Увімкнути Hires. Fix + Цільова роздільна здатність + Кроки: %1$s (0 = як у першому проході) + Шумозаглушення: %1$s + Збільшити та покращити деталі через img2img + Веб Txt2Img @@ -165,7 +206,7 @@ Редактор тегу Виберіть джерело - %1$s файли збережено в Download/SDAI + %1$s файли збережено в Download/PDAI Створене Тип Запит @@ -183,8 +224,13 @@ Скасувати вибір Обрано: %1$s Видалити все + Видалити невподобані Експортувати все + Зберегти все на пристрій Режим вибору + Зображення збережено в галерею + Всі зображення збережено в галерею + Обрані зображення збережено в галерею Параметри AI Налаштування @@ -229,7 +275,7 @@ Завантажуємо випадкове зображення… Генерація Local Diffusion Час очікування: %1$s - Поизиція в черзі: %1$s + Позиція в черзі: %1$s Крок: %1$s/%2$s Експортування вашої галереї… @@ -238,6 +284,9 @@ Експорт галереї Ця функція експортує всі зображення галереї у архів *.zip. Цей процес може тривати довго, якщо у вас багато зображень. Бажаєте продовжити? Ця функція експортує обрані зображення галереї у архів *.zip. Бажаєте продовжити? + Зберегти до галереї пристрою + Це збереже всі зображення до галереї вашого пристрою. Цей процес може зайняти деякий час, якщо у вас багато зображень. Бажаєте продовжити? + Це збереже обрані зображення до галереї вашого пристрою. Бажаєте продовжити? Це призведе до скидання налаштувань програми та видалення всіх створених зображень. Ви бажаєте продовжити? Попередження @@ -255,6 +304,9 @@ Видалити модель Ви впевнені, що хочете видалити модель "%1$s"? + Видалити невподобані зображення + Ви впевнені, що хочете остаточно видалити всі зображення, які не позначено як вподобані? + Очистити креслення Ви впевнені що хочете очистити креслення? @@ -282,6 +334,7 @@ Ви не можете використовувати localhost (127.0.0.1) URL-адресу для підключення до сервера. Мінімальний розмір %1$s Максимальний розмір %1$s + Непідтримувана роздільна здатність для QNN. Використовуйте випадаючий список для вибору коректної роздільної здатності. Помилка під час завантаження Під час обробки вашого запиту сталася помилка. Повторіть спробу пізніше. @@ -299,12 +352,16 @@ Натисніть щоб відкрити додаток. Завантажити кастом модель - Щоб мати можливість завантажити спеціальну модель, вам потрібно дозволити додатку SDAI керувати дозволами на зберігання, оскільки, починаючи з Android 11, це потрібно для доступу до файлів зберігання без обмежень. + Щоб мати можливість завантажити спеціальну модель, вам потрібно дозволити додатку PDAI керувати дозволами на зберігання, оскільки, починаючи з Android 11, це потрібно для доступу до файлів зберігання без обмежень. Налаштувати доступ Щоб використовувати локальну спеціальну модель, помістіть її в локальну папку в пам’яті телефону. Дозволи Остаточна структура папок має бути такою: + Потрібна структура папки QNN: + Сканувати моделі + Моделі не знайдено у вказаній папці.\n\nПереконайтеся, що кожна модель знаходиться у власній підпапці з необхідними файлами. + Знайдено %1$d моделей: Шлях моделі Шлях папки локальної моделі Виберіть папку @@ -343,15 +400,15 @@ Тільки маска Дякую цим чудовим людям за підтримку ❤ - SDAI – це програма для Android, яка: - • Надає Вам можливість створювати зображення за допомогою Stable Diffusion AI;\n• Дає Вам свободу вибору постачальника генерації;\n• Не містить реклами, телеметрії та не шпигує за вами;\n• ПО з відкритим кодом;\n• Ви можете використовувати його безкоштовно. + PDAI – це програма для Android, яка: + • Надає Вам можливість створювати зображення за допомогою Pocket Diffusion;\n• Дає Вам свободу вибору постачальника генерації;\n• Не містить реклами, телеметрії та не шпигує за вами;\n• ПО з відкритим кодом;\n• Ви можете використовувати його безкоштовно. Якщо програма була Вам корисна, та Ви бажаєте подякувати та трохи підтримати розробника, натисніть кнопку нижче. Сховище Push повідомлення Додаток не має дозволів. - Довольте права на %1$s в налаштуваннях. + Дозвольте права на %1$s в налаштуваннях. UI Потік I/O Потік diff --git a/core/localization/src/main/res/values-zh/strings.xml b/core/localization/src/main/res/values-zh/strings.xml index a8c4012d0..73520eca4 100644 --- a/core/localization/src/main/res/values-zh/strings.xml +++ b/core/localization/src/main/res/values-zh/strings.xml @@ -13,6 +13,14 @@ 取消 重试 删除 + 分享 + 保存到图库 + 编辑 + 旋转 + 调整 + 亮度 + 对比度 + 饱和度 下载 宽度 高度 @@ -23,10 +31,12 @@ ✅ 成功 ❌ 失败 + + 版本 %1$s @@ -38,8 +48,8 @@ 不可用 - 图像到图像模式不适用于使用本地设备资源生成图像。 - 要使用图像到图像模式,您可以在配置中选择基于服务器生成的方法。 + ONNX 和 MediaPipe 后端不支持图像到图像模式。 + 您可以切换到 QNN、基于服务器的生成或其他提供商来使用此模式。 DALL·E 目前不支持图像到图像模式。 @@ -52,6 +62,7 @@ 选择 连接 设置 + 启动 分享 在 txt2img 中使用 在 img2img 中使用 @@ -87,7 +98,22 @@ 离线生成 个人服务器 CFG 缩放: %1$s + Distilled CFG Scale: %1$s + 模型类型 + VAE / 文本编码器 采样方法 + 调度器 + ADetailer + 启用 ADetailer + 检测模型 + 置信度: %1$s + 去噪强度: %1$s + Hires. Fix + 启用 Hires. Fix + 放大器 + 比例: %1$s + 步数: %1$s (0 = 相同) + 去噪强度: %1$s 风格预设 Clip 引导预设 恢复面孔 @@ -152,15 +178,30 @@ 关于 Stability AI Stability AI 引擎 + 连接到 Fal AI + Fal AI 是一个用于运行 FLUX 和其他模型的快速无服务器平台。 + 关于 Fal AI + Fal AI 模型 + Fal AI 端点 + 提供你的 Swarm UI URL 模块化的 Stable Diffusion Web 用户界面,专注于易访问、高性能和可扩展性。 - 此配置使用 Microsoft ONNX 运行时,您可以在手机端运行 Stable Diffusion AI 的生成功能,而无需连接至远程服务器/云。 + 此配置使用 Microsoft ONNX 运行时,您可以在手机端运行 Pocket Diffusion 的生成功能,而无需连接至远程服务器/云。 警告!Local Diffusion 功能处于测试版。可能无法获得高质量图像。\n\n此功能在资源受限设备上可能不及预期。生成性能和速度取决于您的手机资源(CPU、RAM)和生成的图像大小(图像越小,生成越快)。 - 此配置使用 Google AI MediaPipe 运行时,您可以在手机端运行 Stable Diffusion AI 的生成功能,而无需连接至远程服务器/云。 + 此配置使用 Google AI MediaPipe 运行时,您可以在手机端运行 Pocket Diffusion 的生成功能,而无需连接至远程服务器/云。 + + 此配置使用高通 QNN SDK,通过 NPU 加速(HTP)和 MNN 后端在设备上快速生成 Stable Diffusion 图像。需要骁龙 8 Gen 1 或更新版本。 + + Hires. Fix (NPU) + 启用 Hires. Fix + 目标分辨率 + 步数: %1$s (0 = 相同) + 降噪强度: %1$s + 放大并通过 img2img 优化细节 网络界面 @@ -194,7 +235,7 @@ 选择来源 - 您在 Download/SDAI 中保存了%1$s张照片。 + 您在 Download/PDAI 中保存了%1$s张照片。 创建日期 类型 提示文本 @@ -212,8 +253,13 @@ 取消全部选择 已选图片:%1$s 全部删除 + 删除未喜欢的 全部导出 + 全部保存到设备 选择模式 + 图像已保存到图库 + 所有图像已保存到图库 + 选定的图像已保存到图库 AI 设置 @@ -275,6 +321,9 @@ 图库导出 这将把所有图库图像导出为 *.zip 压缩包。此过程在图片较多时可能更耗时,您确定继续吗? 这将选定的图库图像导出为 *.zip 存档。您要继续吗? + 保存到设备图库 + 这将把所有图像保存到您的设备图库。如果您有许多图像,此过程可能需要一些时间。您要继续吗? + 这将把选定的图像保存到您的设备图库。您要继续吗? 这将重置应用设置并删除所有生成的图像。您想继续吗? @@ -295,6 +344,10 @@ 删除模型 您确定要删除模型 "%1$s" 吗? + + 删除未喜欢的图像 + 您确定要永久删除所有未标记为喜欢的图像吗? + 清除绘画 您确定要清除绘画吗? @@ -326,6 +379,7 @@ 您不能使用本地主机 (127.0.0.1)URL 来连接到服务器。 最小尺寸为 %1$s 最大尺寸为 %1$s + QNN 不支持此分辨率。请使用下拉列表选择有效的分辨率。 下载失败 处理您的请求时发生错误,请稍后再试。 @@ -345,7 +399,7 @@ 加载自定义模型 - 为了能够加载自定义模型,您需要允许 SDAI 应用管理存储权限,因为从 Android 11 开始,它需要访问非范围存储文件。 + 为了能够加载自定义模型,您需要允许 PDAI 应用管理存储权限,因为从 Android 11 开始,它需要访问非范围存储文件。 设置权限 模型路径 本地模型文件夹路径 @@ -354,6 +408,10 @@ 要使用本地自定义模型,请将其放置在手机存储中的本地文件夹。 权限 最终的文件夹结构应该是: + QNN所需的文件夹结构: + 扫描模型 + 在指定的文件夹中未找到模型。\n\n请确保每个模型都在其自己的子文件夹中,并包含所需的文件。 + 找到 %1$d 个模型: 调试 @@ -394,8 +452,8 @@ 仅掩码 感谢这些了不起的人们提供的支持 ❤ - SDAI 是一款 Android 应用程序,它: - • 赋予您使用 Stable Diffusion AI 创建图像的能力;\n• 让您自由选择生成供应商;\n• 无广告、无遥测,无监控;\n• 是一款开源软件;\n• 欢迎您免费使用。 + PDAI 是一款 Android 应用程序,它: + • 赋予您使用 Pocket Diffusion 创建图像的能力;\n• 让您自由选择生成供应商;\n• 无广告、无遥测,无监控;\n• 是一款开源软件;\n• 欢迎您免费使用。 如果您觉得这个软件有价值,想要表示感谢并给予一些支持,请点击下方的按钮。 存储 @@ -412,7 +470,7 @@ 高级功能\n[稳定扩散]。 [离线]生成\n本地扩散。 自定义应用程序,\n让它成为[您的]! - [自由]选择\n代提供商。 + [自由]选择\n生成提供商。 举报图片 提交举报 diff --git a/core/localization/src/main/res/values/strings.xml b/core/localization/src/main/res/values/strings.xml index c767358e6..a7e370068 100755 --- a/core/localization/src/main/res/values/strings.xml +++ b/core/localization/src/main/res/values/strings.xml @@ -13,9 +13,19 @@ Cancel Retry Delete + Share + Save to gallery + Edit + Rotate + Adjust + Brightness + Contrast + Saturation Download Width Height + Swap dimensions + Aspect ratio Browse Apply Close @@ -23,10 +33,12 @@ ✅ Success ❌ Failure + One Two Three Four Five + Six Version %1$s @@ -35,8 +47,8 @@ Launching application! Not available - Image to Image mode is not available in case you are using your local phone resources to generate images. - You can switch to server based generation in Configuration settings to use Image to Image mode. + Image to Image mode is not supported by ONNX and MediaPipe backends. + You can switch to QNN, server based generation, or another provider in Configuration settings to use Image to Image mode. Image to Image mode is not yet supported by DALL·E. You can switch to any another server based generation in Configuration settings to use Image to Image mode. @@ -47,6 +59,7 @@ Select Connect Setup + Start Share Use in txt2img Use in img2img @@ -74,10 +87,13 @@ ONNX Local Diffusion Google AI MediaPipe (Beta) MediaPipe + Local Diffusion Qualcomm QNN (Beta) + QNN Hugging Face Inference HuggingFace Open AI Stability AI + Fal AI Swarm UI Prompt @@ -89,7 +105,29 @@ Offline generation Own Server CFG Scale: %1$s + Distilled CFG Scale: %1$s + Model type + VAE / Text Encoder + Runtime Sampling method + Scheduler + ADetailer + Enable ADetailer + Detection model + Confidence: %1$s + Denoising: %1$s + Hires. Fix + Enable Hires. Fix + Upscaler + Scale: %1$s + Steps: %1$s (0 = same) + Denoising: %1$s + Hires. Fix (NPU) + Enable Hires. Fix + Target resolution + Steps: %1$s (0 = same) + Denoising: %1$s + Upscale and refine with img2img pass Style preset Clip guidance preset Restore faces @@ -149,15 +187,24 @@ About Stability AI Stability AI Engine + Connect to Fal AI + Fal AI is a fast serverless inference platform for running FLUX and other models. + About Fal AI + Fal AI Model + Fal AI Endpoint + Provide your Swarm UI URL A Modular Stable Diffusion Web-User-Interface, with an emphasis on making tools easily accessible, high performance, and extensibility. Local Diffusion Microsoft ONNX - This configuration uses Microsoft ONNX runtime and allows to run Stable Diffusion AI generations on your phone, with no need to connect to remote server/cloud. + This configuration uses Microsoft ONNX runtime and allows to run Pocket Diffusion generations on your phone, with no need to connect to remote server/cloud. Warning! Local Diffusion functionality is in beta-test. Don\'t expect for high quality images using local mode. \n\nThis implementation may not work well on non-powerful phones. Generation performance and speed depends on your phone resources (CPU, RAM) and the size of generated image (the smaller the image size, the faster the generation). Local Diffusion Google AI MediaPipe - This configuration uses Google AI MediaPipe and allows to run Stable Diffusion AI generations on your phone, with no need to connect to remote server/cloud. + This configuration uses Google AI MediaPipe and allows to run Pocket Diffusion generations on your phone, with no need to connect to remote server/cloud. + + Local Diffusion Qualcomm QNN + This configuration uses Qualcomm QNN SDK with NPU acceleration (HTP) and MNN backend for fast on-device Stable Diffusion generation. Requires Snapdragon 8 Gen 1 or newer. Web UI @@ -188,9 +235,10 @@ Edit tag Select source - You have %1$s photos saved in Download/SDAI + You have %1$s photos saved in Download/PDAI Created Type + Model Prompt Negative prompt Size @@ -206,8 +254,13 @@ Unselect all Selected images: %1$s Delete all + Delete unliked Export all + Save all to device Selection mode + Image saved to gallery + All images saved to gallery + Selected images saved to gallery AI settings App settings @@ -261,11 +314,17 @@ Gallery Export This will export all the gallery images as *.zip archive. This process may be long if you have many images, would you like to proceed? This will export selected gallery images as *.zip archive. Would you like to proceed? + Save to Device Gallery + This will save all images to your device gallery. This process may take some time if you have many images. Would you like to proceed? + This will save selected images to your device gallery. Would you like to proceed? This will reset app settings and delete all the generated images. Do you want to proceed? Warning You are trying to connect to localhost (127.0.0.1) server.\n\nIt may not work unless you have ssh tunneling or another port forwarding mechanism set up on your Android device. + GPU Runtime Warning + GPU runtime warning: The first run at each resolution will be slow as it generates cache files. Subsequent runs will be much faster. + Delete image Are you sure you want to permanently delete this image? @@ -278,6 +337,9 @@ Delete model Are you sure you want to delete model "%1$s"? + Delete unliked images + Are you sure you want to permanently delete all images that are not liked? + Clear drawings Are you sure you want to clear drawings? @@ -305,6 +367,7 @@ You can not use localhost (127.0.0.1) URL to connect to server. Minimum size is %1$s Maximum size is %1$s + Unsupported resolution for QNN. Use dropdown to select valid resolution. Download failed Something wrong happened while processing your request, please try again later. @@ -325,7 +388,7 @@ Load custom model Permissions - To be able to load custom model, you need to allow SDAI app manage storage permissions, because starting from Android 11 it is needed to access non-scoped storage files. + To be able to load custom model, you need to allow PDAI app manage storage permissions, because starting from Android 11 it is needed to access non-scoped storage files. Setup permission Model path Local model folder path @@ -333,6 +396,10 @@ To use local custom model, place it to local folder in your phone storage. The final folder structure should be: + Required QNN folder structure: + Scan for models + No models found in the specified folder.\n\nMake sure each model is in its own subfolder with required files. + Found %1$d model(s): Debugging Local Diffusion @@ -370,8 +437,8 @@ Only masked Thanks to this amazing people for the support ❤ - SDAI is an Android application that: - • Brings you power of creating images with Stable Diffusion AI;\n• Gives you freedom to choose your generation provider;\n• Has no ADs, telemetry and does not spy on you;\n• Is an open source software;\n• You are welcome to use it for free. + PDAI is an Android application that: + • Brings you power of creating images with Pocket Diffusion;\n• Gives you freedom to choose your generation provider;\n• Has no ADs, telemetry and does not spy on you;\n• Is an open source software;\n• You are welcome to use it for free. In case you find this software valuable, and you\'d like to say thanks and show a little support, here is the button below. Storage diff --git a/core/notification/build.gradle.kts b/core/notification/build.gradle.kts index ab2254e71..88be6055f 100644 --- a/core/notification/build.gradle.kts +++ b/core/notification/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.core.notification" + namespace = "dev.minios.pdaiv1.core.notification" } dependencies { diff --git a/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/PushNotificationManager.kt b/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/PushNotificationManager.kt deleted file mode 100644 index 51d727cdd..000000000 --- a/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/PushNotificationManager.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.shifthackz.aisdv1.core.notification - -import android.app.Notification -import androidx.core.app.NotificationCompat -import com.shifthackz.aisdv1.core.model.UiText - -interface PushNotificationManager { - - fun createAndShowInstant(title: UiText, body: UiText) - - fun createAndShowInstant(title: String, body: String) - - fun show(id: Int, notification: Notification) - - fun createNotification( - title: UiText, - body: UiText?, - block: NotificationCompat.Builder.() -> Unit = {}, - ): Notification - - fun createNotification( - title: String, - body: String?, - block: NotificationCompat.Builder.() -> Unit = {}, - ): Notification - - fun createNotificationChannel() -} diff --git a/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/PushNotificationManagerImpl.kt b/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/PushNotificationManagerImpl.kt deleted file mode 100644 index f1decda9f..000000000 --- a/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/PushNotificationManagerImpl.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.shifthackz.aisdv1.core.notification - -import android.Manifest -import android.annotation.SuppressLint -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.Context -import android.content.pm.PackageManager -import android.os.Build -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import com.shifthackz.aisdv1.core.common.extensions.isAppInForeground -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText - -internal class PushNotificationManagerImpl( - private val context: Context, - private val manager: NotificationManagerCompat, -) : PushNotificationManager { - - @SuppressLint("MissingPermission") - override fun createAndShowInstant(title: UiText, body: UiText) { - val inForeground = context.isAppInForeground() - if (inForeground) { - debugLog("App is in foreground, skipping...") - return - } - - val permission = hasNotificationPermission() - if (permission != PackageManager.PERMISSION_GRANTED) { - debugLog("Missing permissions for POST_NOTIFICATIONS, skipping...") - return - } - - val notification = createNotification(title, body) - debugLog("Show PN => title: $title, body: $body") - show(System.currentTimeMillis().toInt(), notification) - } - - override fun createAndShowInstant(title: String, body: String) { - createAndShowInstant(title.asUiText(), body.asUiText()) - } - - @SuppressLint("MissingPermission") - override fun show(id: Int, notification: Notification) { - createNotificationChannel() - manager.notify(id, notification) - } - - override fun createNotification( - title: UiText, - body: UiText?, - block: NotificationCompat.Builder.() -> Unit - ): Notification = with( - NotificationCompat.Builder(context, SDAI_NOTIFICATION_CHANNEL_ID) - ) { - setSmallIcon(R.drawable.ic_notification) - setContentTitle(title.asString(context)) - body?.asString(context)?.let { - setContentText(it) - } - apply(block) - build() - } - - override fun createNotification( - title: String, - body: String?, - block: NotificationCompat.Builder.() -> Unit - ): Notification { - return createNotification(title.asUiText(), body?.asUiText(), block) - } - - override fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (manager.getNotificationChannel(SDAI_NOTIFICATION_CHANNEL_ID) == null) { - debugLog("Creating notification channel") - - manager.createNotificationChannel( - NotificationChannel( - SDAI_NOTIFICATION_CHANNEL_ID, - "SDAI Notifications", - NotificationManager.IMPORTANCE_HIGH, - ).also { channel -> - channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC - } - ) - } - } - } - - private fun hasNotificationPermission(): Int { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) - } else { - PackageManager.PERMISSION_GRANTED - } - } - - companion object { - private const val SDAI_NOTIFICATION_CHANNEL_ID = "SDAI_NOTIFICATION_CHANNEL" - } -} diff --git a/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/di/NotificationModule.kt b/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/di/NotificationModule.kt deleted file mode 100644 index 8e042aad7..000000000 --- a/core/notification/src/main/java/com/shifthackz/aisdv1/core/notification/di/NotificationModule.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.core.notification.di - -import androidx.core.app.NotificationManagerCompat -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.core.notification.PushNotificationManagerImpl -import org.koin.android.ext.koin.androidContext -import org.koin.dsl.module - -val notificationModule = module { - factory { NotificationManagerCompat.from(androidContext()) } - factory { PushNotificationManagerImpl(androidContext(), get()) } -} diff --git a/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/PushNotificationManager.kt b/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/PushNotificationManager.kt new file mode 100644 index 000000000..6bdff69b9 --- /dev/null +++ b/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/PushNotificationManager.kt @@ -0,0 +1,34 @@ +package dev.minios.pdaiv1.core.notification + +import android.app.Notification +import androidx.core.app.NotificationCompat +import dev.minios.pdaiv1.core.model.UiText + +interface PushNotificationManager { + + fun createAndShowInstant(title: UiText, body: UiText) + + fun createAndShowInstant(title: String, body: String) + + fun show(id: Int, notification: Notification) + + fun createNotification( + title: UiText, + body: UiText?, + block: NotificationCompat.Builder.() -> Unit = {}, + ): Notification + + fun createNotification( + title: String, + body: String?, + block: NotificationCompat.Builder.() -> Unit = {}, + ): Notification + + fun createProgressNotification( + title: String, + body: String?, + block: NotificationCompat.Builder.() -> Unit = {}, + ): Notification + + fun createNotificationChannel() +} diff --git a/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/PushNotificationManagerImpl.kt b/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/PushNotificationManagerImpl.kt new file mode 100644 index 000000000..51e73140f --- /dev/null +++ b/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/PushNotificationManagerImpl.kt @@ -0,0 +1,137 @@ +package dev.minios.pdaiv1.core.notification + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import dev.minios.pdaiv1.core.common.extensions.isAppInForeground +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText + +internal class PushNotificationManagerImpl( + private val context: Context, + private val manager: NotificationManagerCompat, +) : PushNotificationManager { + + @SuppressLint("MissingPermission") + override fun createAndShowInstant(title: UiText, body: UiText) { + val inForeground = context.isAppInForeground() + if (inForeground) { + debugLog("App is in foreground, skipping...") + return + } + + val permission = hasNotificationPermission() + if (permission != PackageManager.PERMISSION_GRANTED) { + debugLog("Missing permissions for POST_NOTIFICATIONS, skipping...") + return + } + + val notification = createNotification(title, body) + debugLog("Show PN => title: $title, body: $body") + show(System.currentTimeMillis().toInt(), notification) + } + + override fun createAndShowInstant(title: String, body: String) { + createAndShowInstant(title.asUiText(), body.asUiText()) + } + + @SuppressLint("MissingPermission") + override fun show(id: Int, notification: Notification) { + createNotificationChannel() + manager.notify(id, notification) + } + + override fun createNotification( + title: UiText, + body: UiText?, + block: NotificationCompat.Builder.() -> Unit + ): Notification = with( + NotificationCompat.Builder(context, PDAI_NOTIFICATION_CHANNEL_ID) + ) { + setSmallIcon(R.drawable.ic_notification) + setContentTitle(title.asString(context)) + body?.asString(context)?.let { + setContentText(it) + } + apply(block) + build() + } + + override fun createNotification( + title: String, + body: String?, + block: NotificationCompat.Builder.() -> Unit + ): Notification { + return createNotification(title.asUiText(), body?.asUiText(), block) + } + + override fun createProgressNotification( + title: String, + body: String?, + block: NotificationCompat.Builder.() -> Unit + ): Notification = with( + NotificationCompat.Builder(context, PDAI_PROGRESS_CHANNEL_ID) + ) { + setSmallIcon(R.drawable.ic_notification) + setContentTitle(title) + body?.let { setContentText(it) } + apply(block) + build() + } + + override fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (manager.getNotificationChannel(PDAI_NOTIFICATION_CHANNEL_ID) == null) { + debugLog("Creating notification channel") + + manager.createNotificationChannel( + NotificationChannel( + PDAI_NOTIFICATION_CHANNEL_ID, + "PDAI Notifications", + NotificationManager.IMPORTANCE_HIGH, + ).also { channel -> + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC + } + ) + } + // Create a separate channel for progress notifications with low priority + if (manager.getNotificationChannel(PDAI_PROGRESS_CHANNEL_ID) == null) { + debugLog("Creating progress notification channel") + + manager.createNotificationChannel( + NotificationChannel( + PDAI_PROGRESS_CHANNEL_ID, + "PDAI Progress", + NotificationManager.IMPORTANCE_LOW, + ).also { channel -> + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC + channel.setSound(null, null) + channel.enableVibration(false) + } + ) + } + } + } + + private fun hasNotificationPermission(): Int { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) + } else { + PackageManager.PERMISSION_GRANTED + } + } + + companion object { + private const val PDAI_NOTIFICATION_CHANNEL_ID = "PDAI_NOTIFICATION_CHANNEL" + const val PDAI_PROGRESS_CHANNEL_ID = "PDAI_PROGRESS_CHANNEL" + } +} diff --git a/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/di/NotificationModule.kt b/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/di/NotificationModule.kt new file mode 100644 index 000000000..05014d298 --- /dev/null +++ b/core/notification/src/main/java/dev/minios/pdaiv1/core/notification/di/NotificationModule.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.core.notification.di + +import androidx.core.app.NotificationManagerCompat +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.core.notification.PushNotificationManagerImpl +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val notificationModule = module { + factory { NotificationManagerCompat.from(androidContext()) } + factory { PushNotificationManagerImpl(androidContext(), get()) } +} diff --git a/core/notification/src/main/res/drawable-v35/ic_notification.xml b/core/notification/src/main/res/drawable-v35/ic_notification.xml new file mode 100644 index 000000000..2875312a2 --- /dev/null +++ b/core/notification/src/main/res/drawable-v35/ic_notification.xml @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/notification/src/main/res/drawable/ic_notification.xml b/core/notification/src/main/res/drawable/ic_notification.xml index 3d2234f98..1dcb765f4 100644 --- a/core/notification/src/main/res/drawable/ic_notification.xml +++ b/core/notification/src/main/res/drawable/ic_notification.xml @@ -1,63 +1,9 @@ - - - - - - - - - - - - - - - - + + + diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index a9cb8e048..21c1f1826 100755 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -3,5 +3,5 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.core.ui" + namespace = "dev.minios.pdaiv1.core.ui" } diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/viewmodel/RxViewModel.kt b/core/ui/src/main/java/com/shifthackz/aisdv1/core/viewmodel/RxViewModel.kt deleted file mode 100644 index 5eefdc736..000000000 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/viewmodel/RxViewModel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.core.viewmodel - -import androidx.lifecycle.ViewModel -import com.shifthackz.aisdv1.core.common.contract.RxDisposableContract -import io.reactivex.rxjava3.disposables.CompositeDisposable - -abstract class RxViewModel : ViewModel(), RxDisposableContract { - - override val compositeDisposable = CompositeDisposable() - - override fun onCleared() { - super.onCleared() - compositeDisposable.clear() - } -} diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ComposableExtensions.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ComposableExtensions.kt similarity index 94% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ComposableExtensions.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ComposableExtensions.kt index 5e345763d..c19b2a7b4 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ComposableExtensions.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ComposableExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.extensions +package dev.minios.pdaiv1.core.extensions import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -18,7 +18,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda @Composable fun Modifier.gesturesDisabled() = clickable( diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/LazyGridScopePagingItemsExtension.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/LazyGridScopePagingItemsExtension.kt similarity index 96% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/LazyGridScopePagingItemsExtension.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/LazyGridScopePagingItemsExtension.kt index c9f87e502..7398a5877 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/LazyGridScopePagingItemsExtension.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/LazyGridScopePagingItemsExtension.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.extensions +package dev.minios.pdaiv1.core.extensions import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyGridItemScope diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/RealPathExtensions.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/RealPathExtensions.kt similarity index 98% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/RealPathExtensions.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/RealPathExtensions.kt index 395f73cd9..6b958aa4d 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/RealPathExtensions.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/RealPathExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.extensions +package dev.minios.pdaiv1.core.extensions import android.content.ContentUris import android.content.Context diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/RowScopeExtensions.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/RowScopeExtensions.kt similarity index 92% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/RowScopeExtensions.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/RowScopeExtensions.kt index 4ae86ba75..5e1ff02e4 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/RowScopeExtensions.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/RowScopeExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.extensions +package dev.minios.pdaiv1.core.extensions import androidx.compose.foundation.border import androidx.compose.foundation.layout.RowScope diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ShakeExtensions.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ShakeExtensions.kt similarity index 97% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ShakeExtensions.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ShakeExtensions.kt index c092424a9..16e9505d1 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ShakeExtensions.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ShakeExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.extensions +package dev.minios.pdaiv1.core.extensions import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ShimmerExtensions.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ShimmerExtensions.kt similarity index 97% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ShimmerExtensions.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ShimmerExtensions.kt index 7abafeff3..b586815d4 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/extensions/ShimmerExtensions.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/extensions/ShimmerExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.extensions +package dev.minios.pdaiv1.core.extensions import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/model/UiText.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/model/UiText.kt similarity index 98% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/model/UiText.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/model/UiText.kt index 7cd643c19..c5768b573 100755 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/model/UiText.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/model/UiText.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.core.model +package dev.minios.pdaiv1.core.model import android.content.Context import android.content.res.Resources diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/sharing/SharingExtensions.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/sharing/SharingExtensions.kt similarity index 93% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/sharing/SharingExtensions.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/sharing/SharingExtensions.kt index 94ca78526..8deaaf7aa 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/sharing/SharingExtensions.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/sharing/SharingExtensions.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.core.sharing +package dev.minios.pdaiv1.core.sharing import android.content.Context import android.content.Intent import android.net.Uri -import com.shifthackz.aisdv1.core.common.extensions.uriFromFile +import dev.minios.pdaiv1.core.common.extensions.uriFromFile import java.io.File fun Context.shareText( diff --git a/core/ui/src/main/java/com/shifthackz/aisdv1/core/viewmodel/MviRxViewModel.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/viewmodel/MviRxViewModel.kt similarity index 83% rename from core/ui/src/main/java/com/shifthackz/aisdv1/core/viewmodel/MviRxViewModel.kt rename to core/ui/src/main/java/dev/minios/pdaiv1/core/viewmodel/MviRxViewModel.kt index dff49cfb6..fa712c0e3 100644 --- a/core/ui/src/main/java/com/shifthackz/aisdv1/core/viewmodel/MviRxViewModel.kt +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/viewmodel/MviRxViewModel.kt @@ -1,8 +1,8 @@ @file:Suppress("unused") -package com.shifthackz.aisdv1.core.viewmodel +package dev.minios.pdaiv1.core.viewmodel -import com.shifthackz.aisdv1.core.common.contract.RxDisposableContract +import dev.minios.pdaiv1.core.common.contract.RxDisposableContract import com.shifthackz.android.core.mvi.MviEffect import com.shifthackz.android.core.mvi.MviIntent import com.shifthackz.android.core.mvi.MviState diff --git a/core/ui/src/main/java/dev/minios/pdaiv1/core/viewmodel/RxViewModel.kt b/core/ui/src/main/java/dev/minios/pdaiv1/core/viewmodel/RxViewModel.kt new file mode 100644 index 000000000..19033ab56 --- /dev/null +++ b/core/ui/src/main/java/dev/minios/pdaiv1/core/viewmodel/RxViewModel.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.core.viewmodel + +import androidx.lifecycle.ViewModel +import dev.minios.pdaiv1.core.common.contract.RxDisposableContract +import io.reactivex.rxjava3.disposables.CompositeDisposable + +abstract class RxViewModel : ViewModel(), RxDisposableContract { + + override val compositeDisposable = CompositeDisposable() + + override fun onCleared() { + super.onCleared() + compositeDisposable.clear() + } +} diff --git a/core/validation/build.gradle.kts b/core/validation/build.gradle.kts index 8afadbb03..58bfd59b1 100644 --- a/core/validation/build.gradle.kts +++ b/core/validation/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.core.validation" + namespace = "dev.minios.pdaiv1.core.validation" } dependencies { diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidator.kt b/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidator.kt deleted file mode 100644 index fd9c3ac30..000000000 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidator.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.core.validation.common - -import com.shifthackz.aisdv1.core.validation.ValidationResult - -interface CommonStringValidator { - - operator fun invoke(input: String?) : ValidationResult - - sealed interface Error { - data object Empty : Error - } -} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/di/ValidatorsModule.kt b/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/di/ValidatorsModule.kt deleted file mode 100644 index 81778ad90..000000000 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/di/ValidatorsModule.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.core.validation.di - -import com.shifthackz.aisdv1.core.validation.common.CommonStringValidator -import com.shifthackz.aisdv1.core.validation.common.CommonStringValidatorImpl -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidatorImpl -import com.shifthackz.aisdv1.core.validation.path.FilePathValidator -import com.shifthackz.aisdv1.core.validation.path.FilePathValidatorImpl -import com.shifthackz.aisdv1.core.validation.url.UrlValidator -import com.shifthackz.aisdv1.core.validation.url.UrlValidatorImpl -import org.koin.core.module.dsl.factoryOf -import org.koin.dsl.bind -import org.koin.dsl.module - -val validatorsModule = module { - // !!! Do not use [factoryOf] for DimensionValidatorImpl, it has 2 default Ints in constructor - factory { DimensionValidatorImpl() } - factory { UrlValidatorImpl() } - - factoryOf(::CommonStringValidatorImpl) bind CommonStringValidator::class - factoryOf(::FilePathValidatorImpl) bind FilePathValidator::class -} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidator.kt b/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidator.kt deleted file mode 100644 index 0c6d5bb04..000000000 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidator.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.core.validation.dimension - -import com.shifthackz.aisdv1.core.validation.ValidationResult - -fun interface DimensionValidator { - - operator fun invoke(input: String?): ValidationResult - - sealed interface Error { - data object Empty : Error - data object Unexpected : Error - data class LessThanMinimum(val min: Int) : Error - data class BiggerThanMaximum(val max: Int) : Error - } -} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidator.kt b/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidator.kt deleted file mode 100644 index 6d09d4e36..000000000 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidator.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.core.validation.path - -import com.shifthackz.aisdv1.core.validation.ValidationResult - -interface FilePathValidator { - - operator fun invoke(input: String?): ValidationResult - - sealed interface Error { - data object Empty : Error - data object Invalid : Error - } -} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidator.kt b/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidator.kt deleted file mode 100644 index 69bd43059..000000000 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidator.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.core.validation.url - -import com.shifthackz.aisdv1.core.validation.ValidationResult - -interface UrlValidator { - - operator fun invoke(input: String?): ValidationResult - - sealed interface Error { - data object Empty : Error - data object BadScheme : Error - data object BadPort : Error - data object Invalid : Error - data object Localhost : Error - } -} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/ValidationResult.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/ValidationResult.kt similarity index 80% rename from core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/ValidationResult.kt rename to core/validation/src/main/java/dev/minios/pdaiv1/core/validation/ValidationResult.kt index 94f8d53f3..a39ad0a9f 100644 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/ValidationResult.kt +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/ValidationResult.kt @@ -1,6 +1,6 @@ @file:Suppress("unused") -package com.shifthackz.aisdv1.core.validation +package dev.minios.pdaiv1.core.validation data class ValidationResult( val isValid: Boolean, diff --git a/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidator.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidator.kt new file mode 100644 index 000000000..973d758ba --- /dev/null +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidator.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.core.validation.common + +import dev.minios.pdaiv1.core.validation.ValidationResult + +interface CommonStringValidator { + + operator fun invoke(input: String?) : ValidationResult + + sealed interface Error { + data object Empty : Error + } +} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidatorImpl.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidatorImpl.kt similarity index 77% rename from core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidatorImpl.kt rename to core/validation/src/main/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidatorImpl.kt index bf3c69aea..a02c8535a 100644 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidatorImpl.kt +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidatorImpl.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.core.validation.common +package dev.minios.pdaiv1.core.validation.common -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult internal class CommonStringValidatorImpl : CommonStringValidator { diff --git a/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/di/ValidatorsModule.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/di/ValidatorsModule.kt new file mode 100644 index 000000000..c5830563a --- /dev/null +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/di/ValidatorsModule.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.core.validation.di + +import dev.minios.pdaiv1.core.validation.common.CommonStringValidator +import dev.minios.pdaiv1.core.validation.common.CommonStringValidatorImpl +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidatorImpl +import dev.minios.pdaiv1.core.validation.path.FilePathValidator +import dev.minios.pdaiv1.core.validation.path.FilePathValidatorImpl +import dev.minios.pdaiv1.core.validation.url.UrlValidator +import dev.minios.pdaiv1.core.validation.url.UrlValidatorImpl +import org.koin.core.module.dsl.factoryOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val validatorsModule = module { + // !!! Do not use [factoryOf] for DimensionValidatorImpl, it has 2 default Ints in constructor + factory { DimensionValidatorImpl() } + factory { UrlValidatorImpl() } + + factoryOf(::CommonStringValidatorImpl) bind CommonStringValidator::class + factoryOf(::FilePathValidatorImpl) bind FilePathValidator::class +} diff --git a/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidator.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidator.kt new file mode 100644 index 000000000..99d10481b --- /dev/null +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidator.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.core.validation.dimension + +import dev.minios.pdaiv1.core.validation.ValidationResult + +fun interface DimensionValidator { + + operator fun invoke(input: String?): ValidationResult + + sealed interface Error { + data object Empty : Error + data object Unexpected : Error + data class LessThanMinimum(val min: Int) : Error + data class BiggerThanMaximum(val max: Int) : Error + } +} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidatorImpl.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidatorImpl.kt similarity index 91% rename from core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidatorImpl.kt rename to core/validation/src/main/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidatorImpl.kt index a8448d8c2..8914ae3cf 100644 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidatorImpl.kt +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidatorImpl.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.core.validation.dimension +package dev.minios.pdaiv1.core.validation.dimension -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult internal class DimensionValidatorImpl( private val minimum: Int = MINIMUM, diff --git a/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/path/FilePathValidator.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/path/FilePathValidator.kt new file mode 100644 index 000000000..4291987e7 --- /dev/null +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/path/FilePathValidator.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.core.validation.path + +import dev.minios.pdaiv1.core.validation.ValidationResult + +interface FilePathValidator { + + operator fun invoke(input: String?): ValidationResult + + sealed interface Error { + data object Empty : Error + data object Invalid : Error + } +} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidatorImpl.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/path/FilePathValidatorImpl.kt similarity index 85% rename from core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidatorImpl.kt rename to core/validation/src/main/java/dev/minios/pdaiv1/core/validation/path/FilePathValidatorImpl.kt index ef3c8e968..96b9f4379 100644 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidatorImpl.kt +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/path/FilePathValidatorImpl.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.core.validation.path +package dev.minios.pdaiv1.core.validation.path -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult class FilePathValidatorImpl : FilePathValidator { diff --git a/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/url/UrlValidator.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/url/UrlValidator.kt new file mode 100644 index 000000000..b5af42407 --- /dev/null +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/url/UrlValidator.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.core.validation.url + +import dev.minios.pdaiv1.core.validation.ValidationResult + +interface UrlValidator { + + operator fun invoke(input: String?): ValidationResult + + sealed interface Error { + data object Empty : Error + data object BadScheme : Error + data object BadPort : Error + data object Invalid : Error + data object Localhost : Error + } +} diff --git a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImpl.kt b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/url/UrlValidatorImpl.kt similarity index 95% rename from core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImpl.kt rename to core/validation/src/main/java/dev/minios/pdaiv1/core/validation/url/UrlValidatorImpl.kt index c6ab7e577..b1577c226 100644 --- a/core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImpl.kt +++ b/core/validation/src/main/java/dev/minios/pdaiv1/core/validation/url/UrlValidatorImpl.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.core.validation.url +package dev.minios.pdaiv1.core.validation.url import android.util.Patterns import android.webkit.URLUtil -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult import java.net.URI import java.util.regex.Pattern diff --git a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidatorImplTest.kt b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidatorImplTest.kt similarity index 92% rename from core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidatorImplTest.kt rename to core/validation/src/test/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidatorImplTest.kt index 771f8f0c2..86a240c4f 100644 --- a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/common/CommonStringValidatorImplTest.kt +++ b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/common/CommonStringValidatorImplTest.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.core.validation.common +package dev.minios.pdaiv1.core.validation.common -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult import org.junit.Assert import org.junit.Test diff --git a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidatorImplTest.kt b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidatorImplTest.kt similarity index 95% rename from core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidatorImplTest.kt rename to core/validation/src/test/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidatorImplTest.kt index fff6b4aed..45f6325eb 100644 --- a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/dimension/DimensionValidatorImplTest.kt +++ b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/dimension/DimensionValidatorImplTest.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.core.validation.dimension +package dev.minios.pdaiv1.core.validation.dimension -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult import org.junit.Assert import org.junit.Test diff --git a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidatorImplTest.kt b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/path/FilePathValidatorImplTest.kt similarity index 93% rename from core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidatorImplTest.kt rename to core/validation/src/test/java/dev/minios/pdaiv1/core/validation/path/FilePathValidatorImplTest.kt index e33c20122..9caf272d4 100644 --- a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/path/FilePathValidatorImplTest.kt +++ b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/path/FilePathValidatorImplTest.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.core.validation.path +package dev.minios.pdaiv1.core.validation.path -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult import org.junit.Assert import org.junit.Test diff --git a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImplTest.kt b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/url/UrlValidatorImplTest.kt similarity index 97% rename from core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImplTest.kt rename to core/validation/src/test/java/dev/minios/pdaiv1/core/validation/url/UrlValidatorImplTest.kt index 549f8a612..6846157ed 100644 --- a/core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImplTest.kt +++ b/core/validation/src/test/java/dev/minios/pdaiv1/core/validation/url/UrlValidatorImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.core.validation.url +package dev.minios.pdaiv1.core.validation.url import android.webkit.URLUtil -import com.shifthackz.aisdv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.ValidationResult import io.mockk.every import io.mockk.mockk import io.mockk.mockkConstructor diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 7049652e3..1d79b3160 100755 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.data" + namespace = "dev.minios.pdaiv1.data" testOptions.unitTests.all { test -> test.jvmArgs( "--add-opens", "java.base/java.lang=ALL-UNNAMED", diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/core/CoreGenerationRepository.kt b/data/src/main/java/com/shifthackz/aisdv1/data/core/CoreGenerationRepository.kt deleted file mode 100644 index afe41de0d..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/core/CoreGenerationRepository.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.shifthackz.aisdv1.data.core - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import io.reactivex.rxjava3.core.Single - -internal abstract class CoreGenerationRepository( - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - private val localDataSource: GenerationResultDataSource.Local, - private val preferenceManager: PreferenceManager, - private val backgroundWorkObserver: BackgroundWorkObserver, -) : CoreMediaStoreRepository(preferenceManager, mediaStoreGateway, base64ToBitmapConverter) { - - protected fun insertGenerationResult(ai: AiGenerationResult): Single { - if (backgroundWorkObserver.hasActiveTasks() || preferenceManager.autoSaveAiResults) { - return localDataSource - .insert(ai) - .flatMap { id -> exportToMediaStore(ai).andThen(Single.just(ai.copy(id))) } - } - return Single.just(ai) - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/core/CoreMediaStoreRepository.kt b/data/src/main/java/com/shifthackz/aisdv1/data/core/CoreMediaStoreRepository.kt deleted file mode 100644 index 281befdab..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/core/CoreMediaStoreRepository.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.shifthackz.aisdv1.data.core - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single -import java.io.ByteArrayOutputStream - -internal abstract class CoreMediaStoreRepository( - private val preferenceManager: PreferenceManager, - private val mediaStoreGateway: MediaStoreGateway, - private val base64ToBitmapConverter: Base64ToBitmapConverter, -) { - - protected fun exportToMediaStore(result: AiGenerationResult): Completable { - if (preferenceManager.saveToMediaStore) return export(result) - return Completable.complete() - } - - protected fun getInfo(): Single = Single.create { emitter -> - emitter.onSuccess(mediaStoreGateway.getInfo()) - } - - private fun export(result: AiGenerationResult) = result.image - .let(Base64ToBitmapConverter::Input) - .let(base64ToBitmapConverter::invoke) - .map(Base64ToBitmapConverter.Output::bitmap) - .flatMapCompletable(::processBitmap) - - private fun processBitmap(bmp: Bitmap) = Completable - .fromAction { - val stream = ByteArrayOutputStream() - bmp.compress(Bitmap.CompressFormat.PNG, 100, stream) - mediaStoreGateway.exportToFile( - fileName = "sdai_${System.currentTimeMillis()}", - content = stream.toByteArray(), - ) - } - .onErrorComplete { t -> - errorLog(t) - true - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/di/DataModule.kt b/data/src/main/java/com/shifthackz/aisdv1/data/di/DataModule.kt deleted file mode 100755 index 03c28882c..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/di/DataModule.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.shifthackz.aisdv1.data.di - -val dataModule = (remoteDataSourceModule + localDataSourceModule + repositoryModule) - .toTypedArray() diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/di/LocalDataSourceModule.kt b/data/src/main/java/com/shifthackz/aisdv1/data/di/LocalDataSourceModule.kt deleted file mode 100755 index 7c8748cbc..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/di/LocalDataSourceModule.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.shifthackz.aisdv1.data.di - -import com.shifthackz.aisdv1.data.gateway.DatabaseClearGatewayImpl -import com.shifthackz.aisdv1.data.gateway.mediastore.MediaStoreGatewayFactory -import com.shifthackz.aisdv1.data.local.DownloadableModelLocalDataSource -import com.shifthackz.aisdv1.data.local.EmbeddingsLocalDataSource -import com.shifthackz.aisdv1.data.local.GenerationResultLocalDataSource -import com.shifthackz.aisdv1.data.local.HuggingFaceModelsLocalDataSource -import com.shifthackz.aisdv1.data.local.LorasLocalDataSource -import com.shifthackz.aisdv1.data.local.ServerConfigurationLocalDataSource -import com.shifthackz.aisdv1.data.local.StabilityAiCreditsLocalDataSource -import com.shifthackz.aisdv1.data.local.StableDiffusionHyperNetworksLocalDataSource -import com.shifthackz.aisdv1.data.local.StableDiffusionModelsLocalDataSource -import com.shifthackz.aisdv1.data.local.StableDiffusionSamplersLocalDataSource -import com.shifthackz.aisdv1.data.local.SupportersLocalDataSource -import com.shifthackz.aisdv1.data.local.SwarmUiModelsLocalDataSource -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.datasource.EmbeddingsDataSource -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceModelsDataSource -import com.shifthackz.aisdv1.domain.datasource.LorasDataSource -import com.shifthackz.aisdv1.domain.datasource.ServerConfigurationDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionHyperNetworksDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionModelsDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionSamplersDataSource -import com.shifthackz.aisdv1.domain.datasource.SupportersDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiModelsDataSource -import com.shifthackz.aisdv1.domain.gateway.DatabaseClearGateway -import org.koin.android.ext.koin.androidContext -import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.module - -val localDataSourceModule = module { - singleOf(::DatabaseClearGatewayImpl) bind DatabaseClearGateway::class - // !!! Do not use [factoryOf] for StabilityAiCreditsLocalDataSource, it has default constructor - single { StabilityAiCreditsLocalDataSource() } - factoryOf(::StableDiffusionModelsLocalDataSource) bind StableDiffusionModelsDataSource.Local::class - factoryOf(::StableDiffusionSamplersLocalDataSource) bind StableDiffusionSamplersDataSource.Local::class - factoryOf(::LorasLocalDataSource) bind LorasDataSource.Local::class - factoryOf(::StableDiffusionHyperNetworksLocalDataSource) bind StableDiffusionHyperNetworksDataSource.Local::class - factoryOf(::EmbeddingsLocalDataSource) bind EmbeddingsDataSource.Local::class - factoryOf(::SwarmUiModelsLocalDataSource) bind SwarmUiModelsDataSource.Local::class - factoryOf(::ServerConfigurationLocalDataSource) bind ServerConfigurationDataSource.Local::class - factoryOf(::GenerationResultLocalDataSource) bind GenerationResultDataSource.Local::class - factoryOf(::DownloadableModelLocalDataSource) bind DownloadableModelDataSource.Local::class - factoryOf(::HuggingFaceModelsLocalDataSource) bind HuggingFaceModelsDataSource.Local::class - factoryOf(::SupportersLocalDataSource) bind SupportersDataSource.Local::class - factory { MediaStoreGatewayFactory(androidContext(), get()).invoke() } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/di/RemoteDataSourceModule.kt b/data/src/main/java/com/shifthackz/aisdv1/data/di/RemoteDataSourceModule.kt deleted file mode 100755 index 29912afd1..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/di/RemoteDataSourceModule.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.shifthackz.aisdv1.data.di - -import com.shifthackz.aisdv1.core.common.extensions.fixUrlSlashes -import com.shifthackz.aisdv1.data.gateway.ServerConnectivityGatewayImpl -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.data.remote.DownloadableModelRemoteDataSource -import com.shifthackz.aisdv1.data.remote.HordeGenerationRemoteDataSource -import com.shifthackz.aisdv1.data.remote.HordeStatusSource -import com.shifthackz.aisdv1.data.remote.HuggingFaceGenerationRemoteDataSource -import com.shifthackz.aisdv1.data.remote.HuggingFaceModelsRemoteDataSource -import com.shifthackz.aisdv1.data.remote.OpenAiGenerationRemoteDataSource -import com.shifthackz.aisdv1.data.remote.RandomImageRemoteDataSource -import com.shifthackz.aisdv1.data.remote.ReportRemoteDataSource -import com.shifthackz.aisdv1.data.remote.ServerConfigurationRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StabilityAiCreditsRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StabilityAiEnginesRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StabilityAiGenerationRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StableDiffusionEmbeddingsRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StableDiffusionGenerationRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StableDiffusionHyperNetworksRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StableDiffusionLorasRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StableDiffusionModelsRemoteDataSource -import com.shifthackz.aisdv1.data.remote.StableDiffusionSamplersRemoteDataSource -import com.shifthackz.aisdv1.data.remote.SupportersRemoteDataSource -import com.shifthackz.aisdv1.data.remote.SwarmUiEmbeddingsRemoteDataSource -import com.shifthackz.aisdv1.data.remote.SwarmUiGenerationRemoteDataSource -import com.shifthackz.aisdv1.data.remote.SwarmUiLorasRemoteDataSource -import com.shifthackz.aisdv1.data.remote.SwarmUiModelsRemoteDataSource -import com.shifthackz.aisdv1.data.remote.SwarmUiSessionDataSourceImpl -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.datasource.EmbeddingsDataSource -import com.shifthackz.aisdv1.domain.datasource.HordeGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceModelsDataSource -import com.shifthackz.aisdv1.domain.datasource.LorasDataSource -import com.shifthackz.aisdv1.domain.datasource.OpenAiGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.RandomImageDataSource -import com.shifthackz.aisdv1.domain.datasource.ReportDataSource -import com.shifthackz.aisdv1.domain.datasource.ServerConfigurationDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiEnginesDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionHyperNetworksDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionModelsDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionSamplersDataSource -import com.shifthackz.aisdv1.domain.datasource.SupportersDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiModelsDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.gateway.ServerConnectivityGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.network.connectivity.ConnectivityMonitor -import io.reactivex.rxjava3.core.Single -import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.singleOf -import org.koin.core.parameter.parametersOf -import org.koin.dsl.bind -import org.koin.dsl.module - -val remoteDataSourceModule = module { - single { - ServerUrlProvider { endpoint -> - val prefs = get() - val chain = if (prefs.source == ServerSource.SWARM_UI) { - Single.fromCallable(prefs::swarmUiServerUrl) - } else { - Single.fromCallable(prefs::automatic1111ServerUrl) - } - chain - .map(String::fixUrlSlashes) - .map { baseUrl -> "$baseUrl/$endpoint" } - } - } - singleOf(::HordeStatusSource) bind HordeGenerationDataSource.StatusSource::class - factoryOf(::HordeGenerationRemoteDataSource) bind HordeGenerationDataSource.Remote::class - factoryOf(::HuggingFaceGenerationRemoteDataSource) bind HuggingFaceGenerationDataSource.Remote::class - factoryOf(::OpenAiGenerationRemoteDataSource) bind OpenAiGenerationDataSource.Remote::class - factoryOf(::SwarmUiSessionDataSourceImpl) bind SwarmUiSessionDataSource::class - factoryOf(::SwarmUiGenerationRemoteDataSource) bind SwarmUiGenerationDataSource.Remote::class - factoryOf(::SwarmUiModelsRemoteDataSource) bind SwarmUiModelsDataSource.Remote::class - factoryOf(::SwarmUiLorasRemoteDataSource) bind LorasDataSource.Remote.SwarmUi::class - factoryOf(::SwarmUiEmbeddingsRemoteDataSource) bind EmbeddingsDataSource.Remote.SwarmUi::class - factoryOf(::StableDiffusionGenerationRemoteDataSource) bind StableDiffusionGenerationDataSource.Remote::class - factoryOf(::StableDiffusionSamplersRemoteDataSource) bind StableDiffusionSamplersDataSource.Remote::class - factoryOf(::StableDiffusionModelsRemoteDataSource) bind StableDiffusionModelsDataSource.Remote::class - factoryOf(::StableDiffusionLorasRemoteDataSource) bind LorasDataSource.Remote.Automatic1111::class - factoryOf(::StableDiffusionHyperNetworksRemoteDataSource) bind StableDiffusionHyperNetworksDataSource.Remote::class - factoryOf(::StableDiffusionEmbeddingsRemoteDataSource) bind EmbeddingsDataSource.Remote.Automatic1111::class - factoryOf(::ServerConfigurationRemoteDataSource) bind ServerConfigurationDataSource.Remote::class - factoryOf(::RandomImageRemoteDataSource) bind RandomImageDataSource.Remote::class - factoryOf(::DownloadableModelRemoteDataSource) bind DownloadableModelDataSource.Remote::class - factoryOf(::SupportersRemoteDataSource) bind SupportersDataSource.Remote::class - factoryOf(::HuggingFaceModelsRemoteDataSource) bind HuggingFaceModelsDataSource.Remote::class - factoryOf(::StabilityAiGenerationRemoteDataSource) bind StabilityAiGenerationDataSource.Remote::class - factoryOf(::StabilityAiCreditsRemoteDataSource) bind StabilityAiCreditsDataSource.Remote::class - factoryOf(::StabilityAiEnginesRemoteDataSource) bind StabilityAiEnginesDataSource.Remote::class - factoryOf(::ReportRemoteDataSource) bind ReportDataSource.Remote::class - - factory { - val lambda: () -> Boolean = { - val prefs = get() - prefs.source == ServerSource.AUTOMATIC1111 || prefs.source == ServerSource.SWARM_UI - } - val monitor = get { parametersOf(lambda) } - ServerConnectivityGatewayImpl(monitor, get()) - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/di/RepositoryModule.kt b/data/src/main/java/com/shifthackz/aisdv1/data/di/RepositoryModule.kt deleted file mode 100755 index 1358c8a9f..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/di/RepositoryModule.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.shifthackz.aisdv1.data.di - -import android.content.Context -import android.os.PowerManager -import com.shifthackz.aisdv1.data.repository.DownloadableModelRepositoryImpl -import com.shifthackz.aisdv1.data.repository.EmbeddingsRepositoryImpl -import com.shifthackz.aisdv1.data.repository.GenerationResultRepositoryImpl -import com.shifthackz.aisdv1.data.repository.HordeGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.HuggingFaceGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.HuggingFaceModelsRepositoryImpl -import com.shifthackz.aisdv1.data.repository.LocalDiffusionGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.LorasRepositoryImpl -import com.shifthackz.aisdv1.data.repository.MediaPipeGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.OpenAiGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.RandomImageRepositoryImpl -import com.shifthackz.aisdv1.data.repository.ReportRepositoryImpl -import com.shifthackz.aisdv1.data.repository.ServerConfigurationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.StabilityAiCreditsRepositoryImpl -import com.shifthackz.aisdv1.data.repository.StabilityAiEnginesRepositoryImpl -import com.shifthackz.aisdv1.data.repository.StabilityAiGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.StableDiffusionGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.StableDiffusionHyperNetworksRepositoryImpl -import com.shifthackz.aisdv1.data.repository.StableDiffusionModelsRepositoryImpl -import com.shifthackz.aisdv1.data.repository.StableDiffusionSamplersRepositoryImpl -import com.shifthackz.aisdv1.data.repository.SupportersRepositoryImpl -import com.shifthackz.aisdv1.data.repository.SwarmUiGenerationRepositoryImpl -import com.shifthackz.aisdv1.data.repository.SwarmUiModelsRepositoryImpl -import com.shifthackz.aisdv1.data.repository.TemporaryGenerationResultRepositoryImpl -import com.shifthackz.aisdv1.data.repository.WakeLockRepositoryImpl -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository -import com.shifthackz.aisdv1.domain.repository.EmbeddingsRepository -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository -import com.shifthackz.aisdv1.domain.repository.HuggingFaceModelsRepository -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.LorasRepository -import com.shifthackz.aisdv1.domain.repository.MediaPipeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.OpenAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.RandomImageRepository -import com.shifthackz.aisdv1.domain.repository.ReportRepository -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository -import com.shifthackz.aisdv1.domain.repository.StabilityAiCreditsRepository -import com.shifthackz.aisdv1.domain.repository.StabilityAiEnginesRepository -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionHyperNetworksRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionModelsRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionSamplersRepository -import com.shifthackz.aisdv1.domain.repository.SupportersRepository -import com.shifthackz.aisdv1.domain.repository.SwarmUiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.SwarmUiModelsRepository -import com.shifthackz.aisdv1.domain.repository.TemporaryGenerationResultRepository -import com.shifthackz.aisdv1.domain.repository.WakeLockRepository -import org.koin.android.ext.koin.androidContext -import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.module - -val repositoryModule = module { - single { - WakeLockRepositoryImpl { - androidContext().getSystemService(Context.POWER_SERVICE) as PowerManager - } - } - - singleOf(::TemporaryGenerationResultRepositoryImpl) bind TemporaryGenerationResultRepository::class - factoryOf(::LocalDiffusionGenerationRepositoryImpl) bind LocalDiffusionGenerationRepository::class - factoryOf(::MediaPipeGenerationRepositoryImpl) bind MediaPipeGenerationRepository::class - factoryOf(::HordeGenerationRepositoryImpl) bind HordeGenerationRepository::class - factoryOf(::HuggingFaceGenerationRepositoryImpl) bind HuggingFaceGenerationRepository::class - factoryOf(::OpenAiGenerationRepositoryImpl) bind OpenAiGenerationRepository::class - factoryOf(::SwarmUiGenerationRepositoryImpl) bind SwarmUiGenerationRepository::class - factoryOf(::SwarmUiModelsRepositoryImpl) bind SwarmUiModelsRepository::class - factoryOf(::StabilityAiGenerationRepositoryImpl) bind StabilityAiGenerationRepository::class - factoryOf(::StabilityAiCreditsRepositoryImpl) bind StabilityAiCreditsRepository::class - factoryOf(::StabilityAiEnginesRepositoryImpl) bind StabilityAiEnginesRepository::class - factoryOf(::StableDiffusionGenerationRepositoryImpl) bind StableDiffusionGenerationRepository::class - factoryOf(::StableDiffusionModelsRepositoryImpl) bind StableDiffusionModelsRepository::class - factoryOf(::StableDiffusionSamplersRepositoryImpl) bind StableDiffusionSamplersRepository::class - factoryOf(::LorasRepositoryImpl) bind LorasRepository::class - factoryOf(::StableDiffusionHyperNetworksRepositoryImpl) bind StableDiffusionHyperNetworksRepository::class - factoryOf(::EmbeddingsRepositoryImpl) bind EmbeddingsRepository::class - factoryOf(::ServerConfigurationRepositoryImpl) bind ServerConfigurationRepository::class - factoryOf(::GenerationResultRepositoryImpl) bind GenerationResultRepository::class - factoryOf(::RandomImageRepositoryImpl) bind RandomImageRepository::class - factoryOf(::DownloadableModelRepositoryImpl) bind DownloadableModelRepository::class - factoryOf(::HuggingFaceModelsRepositoryImpl) bind HuggingFaceModelsRepository::class - factoryOf(::SupportersRepositoryImpl) bind SupportersRepository::class - factoryOf(::ReportRepositoryImpl) bind ReportRepository::class -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/DatabaseClearGatewayImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/gateway/DatabaseClearGatewayImpl.kt deleted file mode 100644 index a7de8798c..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/DatabaseClearGatewayImpl.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.data.gateway - -import com.shifthackz.aisdv1.domain.gateway.DatabaseClearGateway -import com.shifthackz.aisdv1.storage.gateway.GatewayClearCacheDb -import com.shifthackz.aisdv1.storage.gateway.GatewayClearPersistentDb -import io.reactivex.rxjava3.core.Completable - -internal class DatabaseClearGatewayImpl( - private val gatewayClearCacheDb: GatewayClearCacheDb, - private val gatewayClearPersistentDb: GatewayClearPersistentDb, -) : DatabaseClearGateway { - - override fun clearSessionScopeDb(): Completable = Completable.fromAction { - gatewayClearCacheDb() - } - - override fun clearStorageScopeDb(): Completable = Completable.fromAction { - gatewayClearPersistentDb() - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/ServerConnectivityGatewayImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/gateway/ServerConnectivityGatewayImpl.kt deleted file mode 100644 index 7257cb46a..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/ServerConnectivityGatewayImpl.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.data.gateway - -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.gateway.ServerConnectivityGateway -import com.shifthackz.aisdv1.network.connectivity.ConnectivityMonitor -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable - -internal class ServerConnectivityGatewayImpl( - private val connectivityMonitor: ConnectivityMonitor, - private val serverUrlProvider: ServerUrlProvider, -) : ServerConnectivityGateway { - - override fun observe(): Flowable = serverUrlProvider("") - .flatMapObservable(connectivityMonitor::observe) - .toFlowable(BackpressureStrategy.LATEST) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayFactory.kt b/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayFactory.kt deleted file mode 100644 index 72e2d145b..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayFactory.kt +++ /dev/null @@ -1,24 +0,0 @@ -@file:Suppress("DEPRECATION") - -package com.shifthackz.aisdv1.data.gateway.mediastore - -import android.content.Context -import com.shifthackz.aisdv1.core.common.extensions.shouldUseNewMediaStore -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway - -internal class MediaStoreGatewayFactory( - private val context: Context, - private val fileProviderDescriptor: FileProviderDescriptor, -) { - - operator fun invoke(): MediaStoreGateway { - if (shouldUseNewMediaStore()) { - debugLog("Using Tiramisu and higher implementation for MediaStore") - return MediaStoreGatewayImpl(context, fileProviderDescriptor) - } - debugLog("Using deprecated implementation for MediaStore") - return MediaStoreGatewayOldImpl() - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayOldImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayOldImpl.kt deleted file mode 100644 index eaf8b3126..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayOldImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.shifthackz.aisdv1.data.gateway.mediastore - -import android.net.Uri -import android.os.Environment -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import java.io.File - -/** - * Implementation to support old Android versions (12 and lower). - * - * - * Working on: - * - Android 9 API 28 (Emulator) - * - Android 10 API 29 (Emulator) - * - Android 11 API 30 (Emulator) - * - Android 12 API 31 (Emulator) - * - * Not working on: - * - Android 12L API 32 (Emulator) - */ -@Deprecated("Deprecated since Android 12, it is here to support old devices.") -internal class MediaStoreGatewayOldImpl : MediaStoreGateway { - - override fun exportToFile(fileName: String, content: ByteArray) { - val dirPath = Environment.getExternalStorageDirectory().path + DIR_PATH - val dir = File(dirPath) - if (!dir.exists()) dir.mkdirs() - val file = File("${dirPath}/${fileName}.jpg") - if (!file.exists()) file.createNewFile() - file.writeBytes(content) - } - - override fun getInfo(): MediaStoreInfo { - val dirPath = Environment.getExternalStorageDirectory().path + DIR_PATH - val dir = File(dirPath) - if (dir.exists() && dir.isDirectory) { - return MediaStoreInfo( - count = dir.listFiles()?.size ?: 0, - folderUri = Uri.fromFile(dir), - ) - } - return MediaStoreInfo() - } - - companion object { - private const val DIR_PATH = "/Download/SDAI" - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/DownloadableModelLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/DownloadableModelLocalDataSource.kt deleted file mode 100644 index 4ab031c2c..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/DownloadableModelLocalDataSource.kt +++ /dev/null @@ -1,139 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.storage.db.persistent.dao.LocalModelDao -import com.shifthackz.aisdv1.storage.db.persistent.entity.LocalModelEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single -import java.io.File - -internal class DownloadableModelLocalDataSource( - private val fileProviderDescriptor: FileProviderDescriptor, - private val dao: LocalModelDao, - private val preferenceManager: PreferenceManager, - private val buildInfoProvider: BuildInfoProvider, -) : DownloadableModelDataSource.Local { - - override fun getAllOnnx() = dao - .queryByType(LocalAiModel.Type.ONNX.key) - .map(List::mapEntityToDomain) - .map { models -> - buildList { - addAll(models) - if (buildInfoProvider.type != BuildType.PLAY) { - add(LocalAiModel.CustomOnnx) - } - } - } - .flatMap { models -> models.withLocalData() } - - override fun getAllMediaPipe(): Single> = dao - .queryByType(LocalAiModel.Type.MediaPipe.key) - .map(List::mapEntityToDomain) - .map { models -> - buildList { - addAll(models) - if (buildInfoProvider.type != BuildType.PLAY) { - add(LocalAiModel.CustomMediaPipe) - } - } - } - .flatMap { models -> models.withLocalData() } - - override fun getById(id: String): Single { - val chain = when (id) { - LocalAiModel.CustomOnnx.id -> Single.just(LocalAiModel.CustomOnnx) - LocalAiModel.CustomMediaPipe.id -> Single.just(LocalAiModel.CustomMediaPipe) - else -> dao - .queryById(id) - .map(LocalModelEntity::mapEntityToDomain) - } - return chain.flatMap { model -> model.withLocalData() } - } - - override fun getSelectedOnnx() = Single - .just(preferenceManager.localOnnxModelId) - .flatMap(::getById) - .onErrorResumeNext { Single.error(IllegalStateException("No selected model.")) } - - override fun observeAllOnnx(): Flowable> = dao - .observeByType(LocalAiModel.Type.ONNX.key) - .map(List::mapEntityToDomain) - .map { models -> - buildList { - addAll(models) - if (buildInfoProvider.type != BuildType.PLAY) add(LocalAiModel.CustomOnnx) - } - } - .flatMap { models -> models.withLocalData().toFlowable() } - - override fun save(list: List) = list - .filter { it.id != LocalAiModel.CustomOnnx.id } - .mapDomainToEntity() - .let(dao::insertList) - - override fun delete(id: String): Completable = Completable.fromAction { - getLocalModelDirectory(id).deleteRecursively() - } - - private fun isDownloaded(model: LocalAiModel) = Single.create { emitter -> - try { - when (model.id) { - LocalAiModel.CustomOnnx.id, - LocalAiModel.CustomMediaPipe.id -> emitter.onSuccess(true) - - else -> { - - when (model.type) { - LocalAiModel.Type.ONNX -> { - val files = getLocalModelFiles(model.id).filter { it.isDirectory } - emitter.onSuccess(files.size == 4) - } - - LocalAiModel.Type.MediaPipe -> { - val files = getLocalModelFiles(model.id) - emitter.onSuccess(files.isNotEmpty()) - } - } - } - } - } catch (e: Exception) { - if (!emitter.isDisposed) emitter.onSuccess(false) - } - } - - private fun getLocalModelDirectory(id: String): File { - return File("${fileProviderDescriptor.localModelDirPath}/${id}") - } - - private fun getLocalModelFiles(id: String): List { - val localModelDir = getLocalModelDirectory(id) - if (!localModelDir.exists()) return emptyList() - return localModelDir.listFiles()?.toList() ?: emptyList() - } - - private fun List.withLocalData() = Observable - .fromIterable(this) - .flatMapSingle { model -> model.withLocalData() } - .toList() - - private fun LocalAiModel.withLocalData() = isDownloaded(this) - .map { downloaded -> - copy( - downloaded = downloaded, - selected = when (this.type) { - LocalAiModel.Type.ONNX -> preferenceManager.localOnnxModelId == id - LocalAiModel.Type.MediaPipe -> preferenceManager.localMediaPipeModelId == id - }, - ) - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/EmbeddingsLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/EmbeddingsLocalDataSource.kt deleted file mode 100644 index 66db4d3c3..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/EmbeddingsLocalDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.EmbeddingsDataSource -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionEmbeddingDao -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity - -internal class EmbeddingsLocalDataSource( - private val dao: StableDiffusionEmbeddingDao, -) : EmbeddingsDataSource.Local { - - override fun getEmbeddings() = dao - .queryAll() - .map(List::mapEntityToDomain) - - override fun insertEmbeddings(list: List) = dao - .deleteAll() - .andThen(dao.insertList(list.mapDomainToEntity())) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/GenerationResultLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/GenerationResultLocalDataSource.kt deleted file mode 100644 index 90c7bd57e..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/GenerationResultLocalDataSource.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.storage.db.persistent.dao.GenerationResultDao -import com.shifthackz.aisdv1.storage.db.persistent.entity.GenerationResultEntity -import io.reactivex.rxjava3.core.Single - -internal class GenerationResultLocalDataSource( - private val dao: GenerationResultDao, -) : GenerationResultDataSource.Local { - - override fun insert(result: AiGenerationResult) = result - .mapDomainToEntity() - .let(dao::insert) - - override fun queryAll(): Single> = dao - .query() - .map(List::mapEntityToDomain) - - override fun queryPage(limit: Int, offset: Int) = dao - .queryPage(limit, offset) - .map(List::mapEntityToDomain) - - override fun queryById(id: Long) = dao - .queryById(id) - .map(GenerationResultEntity::mapEntityToDomain) - - override fun queryByIdList(idList: List) = dao - .queryByIdList(idList) - .map(List::mapEntityToDomain) - - override fun deleteById(id: Long) = dao.deleteById(id) - - override fun deleteByIdList(idList: List) = dao.deleteByIdList(idList) - - override fun deleteAll() = dao.deleteAll() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/HuggingFaceModelsLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/HuggingFaceModelsLocalDataSource.kt deleted file mode 100644 index ba54a62b9..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/HuggingFaceModelsLocalDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceModelsDataSource -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import com.shifthackz.aisdv1.storage.db.persistent.dao.HuggingFaceModelDao -import com.shifthackz.aisdv1.storage.db.persistent.entity.HuggingFaceModelEntity - -internal class HuggingFaceModelsLocalDataSource( - private val dao: HuggingFaceModelDao, -) : HuggingFaceModelsDataSource.Local { - - override fun getAll() = dao - .query() - .map(List::mapEntityToDomain) - - override fun save(models: List) = dao - .deleteAll() - .andThen(dao.insertList(models.mapDomainToEntity())) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/LorasLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/LorasLocalDataSource.kt deleted file mode 100644 index e38771e68..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/LorasLocalDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.LorasDataSource -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionLoraDao -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionLoraEntity - -internal class LorasLocalDataSource( - private val dao: StableDiffusionLoraDao, -) : LorasDataSource.Local { - - override fun getLoras() = dao - .queryAll() - .map(List::mapEntityToDomain) - - override fun insertLoras(loras: List) = dao - .deleteAll() - .andThen(dao.insertList(loras.mapDomainToEntity())) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/ServerConfigurationLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/ServerConfigurationLocalDataSource.kt deleted file mode 100755 index fb6219d5a..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/ServerConfigurationLocalDataSource.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapToDomain -import com.shifthackz.aisdv1.data.mappers.mapToEntity -import com.shifthackz.aisdv1.domain.datasource.ServerConfigurationDataSource -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration -import com.shifthackz.aisdv1.storage.db.cache.dao.ServerConfigurationDao -import com.shifthackz.aisdv1.storage.db.cache.entity.ServerConfigurationEntity - -internal class ServerConfigurationLocalDataSource( - private val dao: ServerConfigurationDao, -) : ServerConfigurationDataSource.Local { - - override fun save(configuration: ServerConfiguration) = dao - .insert(configuration.mapToEntity()) - - override fun get() = dao - .query() - .map(ServerConfigurationEntity::mapToDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionHyperNetworksLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionHyperNetworksLocalDataSource.kt deleted file mode 100644 index d849924a3..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionHyperNetworksLocalDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionHyperNetworksDataSource -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionHyperNetworkDao -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity - -internal class StableDiffusionHyperNetworksLocalDataSource( - private val dao: StableDiffusionHyperNetworkDao, -) : StableDiffusionHyperNetworksDataSource.Local { - - override fun getHyperNetworks() = dao - .queryAll() - .map(List::mapEntityToDomain) - - override fun insertHyperNetworks(list: List) = dao - .deleteAll() - .andThen(dao.insertList(list.mapDomainToEntity())) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionModelsLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionModelsLocalDataSource.kt deleted file mode 100755 index b3a1012cf..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionModelsLocalDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionModelsDataSource -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionModelDao -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionModelEntity - -internal class StableDiffusionModelsLocalDataSource( - private val dao: StableDiffusionModelDao, -) : StableDiffusionModelsDataSource.Local { - - override fun getModels() = dao - .queryAll() - .map(List::mapEntityToDomain) - - override fun insertModels(models: List) = dao - .deleteAll() - .andThen(dao.insertList(models.mapDomainToEntity())) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionSamplersLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionSamplersLocalDataSource.kt deleted file mode 100755 index 7cf608a9f..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/StableDiffusionSamplersLocalDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionSamplersDataSource -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionSamplerDao -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionSamplerEntity - -internal class StableDiffusionSamplersLocalDataSource( - private val dao: StableDiffusionSamplerDao, -) : StableDiffusionSamplersDataSource.Local { - - override fun getSamplers() = dao - .queryAll() - .map(List::mapEntityToDomain) - - override fun insertSamplers(samplers: List) = dao - .deleteAll() - .andThen(dao.insertList(samplers.mapDomainToEntity())) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/SupportersLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/SupportersLocalDataSource.kt deleted file mode 100644 index acc60f3c5..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/SupportersLocalDataSource.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.SupportersDataSource -import com.shifthackz.aisdv1.domain.entity.Supporter -import com.shifthackz.aisdv1.storage.db.persistent.dao.SupporterDao -import com.shifthackz.aisdv1.storage.db.persistent.entity.SupporterEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class SupportersLocalDataSource( - private val dao: SupporterDao, -) : SupportersDataSource.Local { - - override fun save(data: List): Completable = dao - .deleteAll() - .andThen(dao.insertList(data.mapDomainToEntity())) - - override fun getAll(): Single> = dao - .queryAll() - .map(List::mapEntityToDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/SwarmUiModelsLocalDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/local/SwarmUiModelsLocalDataSource.kt deleted file mode 100644 index a40cec23d..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/SwarmUiModelsLocalDataSource.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mappers.mapDomainToEntity -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.domain.datasource.SwarmUiModelsDataSource -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import com.shifthackz.aisdv1.storage.db.cache.dao.SwarmUiModelDao -import com.shifthackz.aisdv1.storage.db.cache.entity.SwarmUiModelEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class SwarmUiModelsLocalDataSource( - private val dao: SwarmUiModelDao, -) : SwarmUiModelsDataSource.Local { - - override fun getModels(): Single> = dao - .queryAll() - .map(List::mapEntityToDomain) - - override fun insertModels(models: List): Completable = dao - .deleteAll() - .andThen(dao.insertList(models.mapDomainToEntity())) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/AiGenerationResultMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/AiGenerationResultMappers.kt deleted file mode 100644 index b1f6bf170..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/AiGenerationResultMappers.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.shifthackz.aisdv1.data.mappers - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.storage.db.persistent.entity.GenerationResultEntity - -//region DOMAIN --> ENTITY -fun List.mapDomainToEntity(): List = - map(AiGenerationResult::mapDomainToEntity) - -fun AiGenerationResult.mapDomainToEntity(): GenerationResultEntity = with(this) { - GenerationResultEntity( - id = id, - imageBase64 = image, - originalImageBase64 = inputImage, - createdAt = createdAt, - generationType = type.key, - prompt = prompt, - negativePrompt = negativePrompt, - width = width, - height = height, - samplingSteps = samplingSteps, - cfgScale = cfgScale, - restoreFaces = restoreFaces, - sampler = sampler, - seed = seed, - subSeed = subSeed, - subSeedStrength = subSeedStrength, - denoisingStrength = denoisingStrength, - hidden = hidden, - ) -} -//endregion - -//region ENTITY --> DOMAIN -fun List.mapEntityToDomain(): List = - map(GenerationResultEntity::mapEntityToDomain) - -fun GenerationResultEntity.mapEntityToDomain(): AiGenerationResult = with(this) { - AiGenerationResult( - id = id, - image = imageBase64, - inputImage = originalImageBase64, - createdAt = createdAt, - type = AiGenerationResult.Type.parse(generationType), - prompt = prompt, - negativePrompt = negativePrompt, - width = width, - height = height, - samplingSteps = samplingSteps, - cfgScale = cfgScale, - restoreFaces = restoreFaces, - sampler = sampler, - seed = seed, - subSeed = subSeed, - subSeedStrength = subSeedStrength, - denoisingStrength = denoisingStrength, - hidden = hidden, - ) -} -//endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ImageToImagePayloadMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ImageToImagePayloadMappers.kt deleted file mode 100644 index 2863d6290..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ImageToImagePayloadMappers.kt +++ /dev/null @@ -1,172 +0,0 @@ -package com.shifthackz.aisdv1.data.mappers - -import com.shifthackz.aisdv1.core.common.math.roundTo -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.StabilityAiClipGuidance -import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset -import com.shifthackz.aisdv1.network.request.HordeGenerationAsyncRequest -import com.shifthackz.aisdv1.network.request.HuggingFaceGenerationRequest -import com.shifthackz.aisdv1.network.request.ImageToImageRequest -import com.shifthackz.aisdv1.network.request.SwarmUiGenerationRequest -import com.shifthackz.aisdv1.network.response.SdGenerationResponse -import java.util.Date - -//region PAYLOAD --> REQUEST -fun ImageToImagePayload.mapToRequest(): ImageToImageRequest = with(this) { - ImageToImageRequest( - initImages = listOf(base64Image), - includeInitImages = true, - mask = base64MaskImage.takeIf(String::isNotBlank), - inPaintingMaskInvert = inPaintingMaskInvert, - inPaintFullResPadding = inPaintFullResPadding, - inPaintingFill = inPaintingFill, - inPaintFullRes = inPaintFullRes, - maskBlur = maskBlur, - denoisingStrength = denoisingStrength, - prompt = prompt, - negativePrompt = negativePrompt, - steps = samplingSteps, - cfgScale = cfgScale, - width = width, - height = height, - restoreFaces = restoreFaces, - seed = seed.trim().ifEmpty { null }, - subSeed = subSeed.trim().ifEmpty { null }, - subSeedStrength = subSeedStrength, - samplerIndex = sampler, - ) -} - -fun ImageToImagePayload.mapToHordeRequest(): HordeGenerationAsyncRequest = with(this) { - HordeGenerationAsyncRequest( - prompt = prompt, - nsfw = nsfw, - sourceProcessing = "img2img", - sourceImage = base64Image, - params = HordeGenerationAsyncRequest.Params( - cfgScale = cfgScale, - width = width, - height = height, - steps = samplingSteps, - seed = seed.trim().ifEmpty { null }, - subSeedStrength = subSeedStrength.takeIf { it >= 0.1 }, - ) - ) -} - -fun ImageToImagePayload.mapToHuggingFaceRequest(): HuggingFaceGenerationRequest = with(this) { - HuggingFaceGenerationRequest( - inputs = base64Image, - parameters = buildMap { - this["width"] = width - this["height"] = height - prompt.trim().takeIf(String::isNotBlank)?.let { - this["text"] = it - } - negativePrompt.trim().takeIf(String::isNotBlank)?.let { - this["negative_prompt"] = it - } - seed.trim().takeIf(String::isNotBlank)?.let { - this["seed"] = it - } - this["num_inference_steps"] = samplingSteps - this["guidance_scale"] = cfgScale - } - ) -} - -fun ImageToImagePayload.mapToStabilityAiRequest() = with(this) { - buildMap { - buildList { - addAll(prompt.mapToStabilityPrompt(1.0)) - addAll(negativePrompt.mapToStabilityPrompt(-1.0)) - }.forEachIndexed { index, stpRaw -> - this["text_prompts[$index][text]"] = stpRaw.text - this["text_prompts[$index][weight]"] = stpRaw.weight.toString() - } - this["image_strength"] = "$denoisingStrength" - this["cfg_scale"] = "$cfgScale" - this["clip_guidance_preset"] = (stabilityAiClipGuidance ?: StabilityAiClipGuidance.NONE).toString() - this["seed"] = (seed.toLongOrNull()?.coerceIn(0L .. 4294967295L) ?: 0L).toString() - this["steps"] = "$samplingSteps" - stabilityAiStylePreset?.takeIf { it != StabilityAiStylePreset.NONE }?.key?.let { - this["style_preset"] = it - } - } -} - -fun ImageToImagePayload.mapToSwarmUiRequest( - sessionId: String, - swarmUiModel: String, -): SwarmUiGenerationRequest = with(this) { - SwarmUiGenerationRequest( - sessionId = sessionId, - model = swarmUiModel, - initImage = base64Image, - initImageCreativity = denoisingStrength.roundTo(2).toString(), - images = 1, - prompt = prompt, - negativePrompt = negativePrompt, - width = width, - height = height, - seed = seed.trim().ifEmpty { null }, - variationSeed = subSeed.trim().ifEmpty { null }, - variationSeedStrength = subSeedStrength.takeIf { it >= 0.1 }?.toString(), - cfgScale = cfgScale, - steps = samplingSteps, - ) -} -//endregion - -//region RESPONSE --> RESULT -fun Pair.mapToAiGenResult(): AiGenerationResult = - let { (payload, response) -> - AiGenerationResult( - id = 0L, - image = response.images?.firstOrNull() ?: "", - inputImage = payload.base64Image, - createdAt = Date(), - type = AiGenerationResult.Type.IMAGE_TO_IMAGE, - denoisingStrength = payload.denoisingStrength, - prompt = payload.prompt, - negativePrompt = payload.negativePrompt, - width = payload.width, - height = payload.height, - samplingSteps = payload.samplingSteps, - cfgScale = payload.cfgScale, - restoreFaces = payload.restoreFaces, - sampler = payload.sampler, - seed = if (payload.seed.trim().isNotEmpty()) payload.seed - else mapSeedFromRemote(response.info), - subSeed = if (payload.subSeed.trim().isNotEmpty()) payload.subSeed - else mapSubSeedFromRemote(response.info), - subSeedStrength = payload.subSeedStrength, - hidden = false, - ) - } - -fun Pair.mapCloudToAiGenResult(): AiGenerationResult = - let { (payload, base64) -> - AiGenerationResult( - id = 0L, - image = base64, - inputImage = payload.base64Image, - createdAt = Date(), - type = AiGenerationResult.Type.IMAGE_TO_IMAGE, - denoisingStrength = payload.denoisingStrength, - prompt = payload.prompt, - negativePrompt = payload.negativePrompt, - width = payload.width, - height = payload.height, - samplingSteps = payload.samplingSteps, - cfgScale = payload.cfgScale, - restoreFaces = payload.restoreFaces, - sampler = payload.sampler, - seed = payload.seed, - subSeed = payload.subSeed, - subSeedStrength = payload.subSeedStrength, - hidden = false, - ) - } -//endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/LocalAiModelMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/LocalAiModelMappers.kt deleted file mode 100644 index 7d7033aa8..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/LocalAiModelMappers.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.shifthackz.aisdv1.data.mappers - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.network.response.DownloadableModelResponse -import com.shifthackz.aisdv1.storage.db.persistent.entity.LocalModelEntity - -//region RAW --> DOMAIN -fun List.mapRawToCheckpointDomain( - type: LocalAiModel.Type, -): List = map { it.mapRawToCheckpointDomain(type) } - -fun DownloadableModelResponse.mapRawToCheckpointDomain( - type: LocalAiModel.Type, -): LocalAiModel = with(this) { - LocalAiModel( - id = id ?: "", - type = type, - name = name ?: "", - size = size ?: "", - sources = sources ?: emptyList(), - ) -} -//endregion - -//region DOMAIN --> ENTITY -fun List.mapDomainToEntity(): List = - map(LocalAiModel::mapDomainToEntity) - -fun LocalAiModel.mapDomainToEntity(): LocalModelEntity = with(this) { - LocalModelEntity(id, type.key, name, size, sources) -} -//endregion - -//region ENTITY --> DOMAIN -fun List.mapEntityToDomain(): List = - map(LocalModelEntity::mapEntityToDomain) - -fun LocalModelEntity.mapEntityToDomain(): LocalAiModel = with(this) { - LocalAiModel(id, LocalAiModel.Type.parse(type), name, size, sources) -} -//endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StabilityAiEngineMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StabilityAiEngineMappers.kt deleted file mode 100644 index 44999ca48..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StabilityAiEngineMappers.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.data.mappers - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine -import com.shifthackz.aisdv1.network.model.StabilityAiEngineRaw - -//region RAW --> DOMAIN -fun List.mapRawToCheckpointDomain(): List = - map(StabilityAiEngineRaw::mapRawToCheckpointDomain) - -fun StabilityAiEngineRaw.mapRawToCheckpointDomain(): StabilityAiEngine = with(this) { - StabilityAiEngine(id ?: "", name ?: "") -} -//endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/TextToImagePayloadMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/TextToImagePayloadMappers.kt deleted file mode 100755 index 5c4589bb6..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/TextToImagePayloadMappers.kt +++ /dev/null @@ -1,197 +0,0 @@ -package com.shifthackz.aisdv1.data.mappers - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.StabilityAiClipGuidance -import com.shifthackz.aisdv1.domain.entity.StabilityAiSampler -import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.network.request.HordeGenerationAsyncRequest -import com.shifthackz.aisdv1.network.request.HuggingFaceGenerationRequest -import com.shifthackz.aisdv1.network.request.OpenAiRequest -import com.shifthackz.aisdv1.network.request.StabilityTextToImageRequest -import com.shifthackz.aisdv1.network.request.SwarmUiGenerationRequest -import com.shifthackz.aisdv1.network.request.TextToImageRequest -import com.shifthackz.aisdv1.network.response.SdGenerationResponse -import java.util.Date - -//region PAYLOAD --> REQUEST -fun TextToImagePayload.mapToRequest(): TextToImageRequest = with(this) { - TextToImageRequest( - prompt = prompt, - negativePrompt = negativePrompt, - steps = samplingSteps, - cfgScale = cfgScale, - width = width, - height = height, - restoreFaces = restoreFaces, - seed = seed.trim().ifEmpty { null }, - subSeed = subSeed.trim().ifEmpty { null }, - subSeedStrength = subSeedStrength, - samplerIndex = sampler, - ) -} - -fun TextToImagePayload.mapToHordeRequest(): HordeGenerationAsyncRequest = with(this) { - HordeGenerationAsyncRequest( - prompt = prompt, - nsfw = nsfw, - sourceProcessing = null, - sourceImage = null, - params = HordeGenerationAsyncRequest.Params( - cfgScale = cfgScale, - width = width, - height = height, - steps = samplingSteps, - seed = seed.trim().ifEmpty { null }, - subSeedStrength = subSeedStrength.takeIf { it >= 0.1 }, - ) - ) -} - -fun TextToImagePayload.mapToHuggingFaceRequest(): HuggingFaceGenerationRequest = with(this) { - HuggingFaceGenerationRequest( - inputs = prompt, - parameters = buildMap { - this["width"] = width - this["height"] = height - negativePrompt.trim().takeIf(String::isNotBlank)?.let { - this["negative_prompt"] = it - } - seed.trim().takeIf(String::isNotBlank)?.let { - this["seed"] = it - } - this["num_inference_steps"] = samplingSteps - this["guidance_scale"] = cfgScale - }, - ) -} - -fun TextToImagePayload.mapToOpenAiRequest(): OpenAiRequest = with(this) { - OpenAiRequest( - prompt = prompt, - model = openAiModel?.alias ?: OpenAiModel.DALL_E_2.alias, - size = "${width}x${height}", - responseFormat = "b64_json", - quality = quality, - style = style, - ) -} - -fun TextToImagePayload.mapToStabilityAiRequest(): StabilityTextToImageRequest = with(this) { - StabilityTextToImageRequest( - height = height, - width = width, - textPrompts = buildList { - addAll(prompt.mapToStabilityPrompt(1.0)) - addAll(negativePrompt.mapToStabilityPrompt(-1.0)) - }, - cfgScale = cfgScale, - clipGuidancePreset = (stabilityAiClipGuidance ?: StabilityAiClipGuidance.NONE).toString(), - sampler = sampler - .takeIf { it != "${StabilityAiSampler.NONE}" } - .takeIf { StabilityAiSampler.entries.map { s -> "$s" }.contains(it) }, - seed = seed.toLongOrNull()?.coerceIn(0L .. 4294967295L) ?: 0L, - steps = samplingSteps, - stylePreset = stabilityAiStylePreset?.takeIf { it != StabilityAiStylePreset.NONE }?.key, - ) -} - -fun TextToImagePayload.mapToSwarmUiRequest( - sessionId: String, - swarmUiModel: String, -): SwarmUiGenerationRequest = with(this) { - SwarmUiGenerationRequest( - sessionId = sessionId, - model = swarmUiModel, - initImage = null, - initImageCreativity = null, - images = 1, - prompt = prompt, - negativePrompt = negativePrompt, - width = width, - height = height, - seed = seed.trim().ifEmpty { null }, - variationSeed = subSeed.trim().ifEmpty { null }, - variationSeedStrength = subSeedStrength.takeIf { it >= 0.1 }?.toString(), - cfgScale = cfgScale, - steps = samplingSteps, - ) -} -//endregion - -//region RESPONSE --> RESULT -fun Pair.mapToAiGenResult(): AiGenerationResult = - let { (payload, response) -> - AiGenerationResult( - id = 0L, - image = response.images?.firstOrNull() ?: "", - inputImage = "", - createdAt = Date(), - type = AiGenerationResult.Type.TEXT_TO_IMAGE, - denoisingStrength = 0f, - prompt = payload.prompt, - negativePrompt = payload.negativePrompt, - width = payload.width, - height = payload.height, - samplingSteps = payload.samplingSteps, - cfgScale = payload.cfgScale, - restoreFaces = payload.restoreFaces, - sampler = payload.sampler, - seed = if (payload.seed.trim().isNotEmpty()) payload.seed - else mapSeedFromRemote(response.info), - subSeed = if (payload.subSeed.trim().isNotEmpty()) payload.subSeed - else mapSubSeedFromRemote(response.info), - subSeedStrength = payload.subSeedStrength, - hidden = false, - ) - } - -fun Pair.mapCloudToAiGenResult(): AiGenerationResult = - let { (payload, base64) -> - AiGenerationResult( - id = 0L, - image = base64, - inputImage = "", - createdAt = Date(), - type = AiGenerationResult.Type.TEXT_TO_IMAGE, - denoisingStrength = 0f, - prompt = payload.prompt, - negativePrompt = payload.negativePrompt, - width = payload.width, - height = payload.height, - samplingSteps = payload.samplingSteps, - cfgScale = payload.cfgScale, - restoreFaces = payload.restoreFaces, - sampler = payload.sampler, - seed = payload.seed, - subSeed = payload.subSeed, - subSeedStrength = payload.subSeedStrength, - hidden = false, - ) - } - -fun Pair.mapLocalDiffusionToAiGenResult(): AiGenerationResult = - let { (payload, base64) -> - AiGenerationResult( - id = 0L, - image = base64, - inputImage = "", - createdAt = Date(), - type = AiGenerationResult.Type.TEXT_TO_IMAGE, - denoisingStrength = 0f, - prompt = payload.prompt, - negativePrompt = payload.negativePrompt, - width = payload.width, - height = payload.height, - samplingSteps = payload.samplingSteps, - cfgScale = payload.cfgScale, - restoreFaces = payload.restoreFaces, - sampler = payload.sampler, - seed = payload.seed, - subSeed = payload.subSeed, - subSeedStrength = payload.subSeedStrength, - hidden = false, - ) - } -//endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt deleted file mode 100644 index 56191be31..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt +++ /dev/null @@ -1,300 +0,0 @@ -package com.shifthackz.aisdv1.data.preference - -import android.content.SharedPreferences -import com.shifthackz.aisdv1.core.common.extensions.fixUrlSlashes -import com.shifthackz.aisdv1.core.common.extensions.shouldUseNewMediaStore -import com.shifthackz.aisdv1.core.common.file.LOCAL_DIFFUSION_CUSTOM_PATH -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.entity.FeatureTag -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.android.core.preferences.delegates -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.BehaviorSubject - -class PreferenceManagerImpl( - preferences: SharedPreferences, -) : PreferenceManager { - - private val preferencesChangedSubject: BehaviorSubject = - BehaviorSubject.createDefault(Unit) - - override var automatic1111ServerUrl: String by preferences.delegates.complexString( - key = KEY_SERVER_URL, - default = "", - serialize = { it.fixUrlSlashes() }, - deserialize = { it.fixUrlSlashes() }, - onChanged = ::onPreferencesChanged, - ) - - override var swarmUiServerUrl: String by preferences.delegates.complexString( - key = KEY_SWARM_SERVER_URL, - default = "", - serialize = { it.fixUrlSlashes() }, - deserialize = { it.fixUrlSlashes() }, - onChanged = ::onPreferencesChanged, - ) - - override var swarmUiModel: String by preferences.delegates.string( - key = KEY_SWARM_MODEL, - onChanged = ::onPreferencesChanged, - ) - - override var demoMode: Boolean by preferences.delegates.boolean( - key = KEY_DEMO_MODE, - onChanged = ::onPreferencesChanged, - ) - - override var developerMode: Boolean by preferences.delegates.boolean( - key = KEY_DEVELOPER_MODE, - onChanged = ::onPreferencesChanged, - ) - - override var localMediaPipeCustomModelPath: String by preferences.delegates.string( - key = KEY_MEDIA_PIPE_CUSTOM_MODEL_PATH, - default = LOCAL_DIFFUSION_CUSTOM_PATH, - ) - - override var localOnnxCustomModelPath: String by preferences.delegates.string( - key = KEY_LOCAL_DIFFUSION_CUSTOM_MODEL_PATH, - default = LOCAL_DIFFUSION_CUSTOM_PATH, - ) - - override var localOnnxAllowCancel: Boolean by preferences.delegates.boolean( - key = KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, - onChanged = ::onPreferencesChanged, - ) - - override var localOnnxSchedulerThread: SchedulersToken by preferences.delegates.complexInt( - key = KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD, - default = SchedulersToken.COMPUTATION, - serialize = { token -> token.ordinal }, - deserialize = { index -> SchedulersToken.entries[index] }, - onChanged = ::onPreferencesChanged, - ) - - override var monitorConnectivity: Boolean by preferences.delegates.complexBoolean( - key = KEY_MONITOR_CONNECTIVITY, - default = false, - serialize = { it }, - deserialize = { if (!source.featureTags.contains(FeatureTag.OwnServer)) false else it }, - onChanged = ::onPreferencesChanged, - ) - - override var autoSaveAiResults: Boolean by preferences.delegates.boolean( - key = KEY_AI_AUTO_SAVE, - default = true, - onChanged = ::onPreferencesChanged, - ) - - override var saveToMediaStore: Boolean by preferences.delegates.boolean( - key = KEY_SAVE_TO_MEDIA_STORE, - default = shouldUseNewMediaStore(), - onChanged = ::onPreferencesChanged, - ) - - override var formAdvancedOptionsAlwaysShow: Boolean by preferences.delegates.boolean( - key = KEY_FORM_ALWAYS_SHOW_ADVANCED_OPTIONS, - onChanged = ::onPreferencesChanged, - ) - - override var formPromptTaggedInput: Boolean by preferences.delegates.boolean( - key = KEY_FORM_PROMPT_TAGGED_INPUT, - default = false, - onChanged = ::onPreferencesChanged, - ) - - override var source: ServerSource by preferences.delegates.complexString( - key = KEY_SERVER_SOURCE, - default = ServerSource.AUTOMATIC1111, - serialize = { source -> source.key }, - deserialize = { key -> ServerSource.parse(key) }, - onChanged = ::onPreferencesChanged, - ) - - override var sdModel: String by preferences.delegates.string( - key = KEY_SD_MODEL, - onChanged = ::onPreferencesChanged, - ) - - override var hordeApiKey: String by preferences.delegates.string( - key = KEY_HORDE_API_KEY, - onChanged = ::onPreferencesChanged, - ) - - override var openAiApiKey: String by preferences.delegates.string( - key = KEY_OPEN_AI_API_KEY, - onChanged = ::onPreferencesChanged, - ) - - override var huggingFaceApiKey: String by preferences.delegates.string( - key = KEY_HUGGING_FACE_API_KEY, - onChanged = ::onPreferencesChanged, - ) - - override var huggingFaceModel: String by preferences.delegates.string( - key = KEY_HUGGING_FACE_MODEL_KEY, - default = HuggingFaceModel.default.alias, - onChanged = ::onPreferencesChanged, - ) - - override var stabilityAiApiKey: String by preferences.delegates.string( - key = KEY_STABILITY_AI_API_KEY, - onChanged = ::onPreferencesChanged, - ) - - override var stabilityAiEngineId: String by preferences.delegates.string( - key = KEY_STABILITY_AI_ENGINE_ID_KEY, - onChanged = ::onPreferencesChanged, - ) - - override var onBoardingComplete: Boolean by preferences.delegates.boolean( - key = KEY_ON_BOARDING_COMPLETE, - ) - - override var forceSetupAfterUpdate: Boolean by preferences.delegates.boolean( - key = KEY_FORCE_SETUP_AFTER_UPDATE, - default = true, - onChanged = ::onPreferencesChanged, - ) - - override var localOnnxModelId: String by preferences.delegates.string( - key = KEY_LOCAL_MODEL_ID, - onChanged = ::onPreferencesChanged, - ) - - override var localOnnxUseNNAPI: Boolean by preferences.delegates.boolean( - key = KEY_LOCAL_NN_API, - onChanged = ::onPreferencesChanged, - ) - - override var localMediaPipeModelId: String by preferences.delegates.string( - key = KEY_MEDIA_PIPE_MODEL_ID, - onChanged = ::onPreferencesChanged, - ) - - override var designUseSystemColorPalette: Boolean by preferences.delegates.boolean( - key = KEY_DESIGN_DYNAMIC_COLORS, - onChanged = ::onPreferencesChanged, - ) - - override var designUseSystemDarkTheme: Boolean by preferences.delegates.boolean( - key = KEY_DESIGN_SYSTEM_DARK_THEME, - default = true, - onChanged = ::onPreferencesChanged, - ) - - override var designDarkTheme: Boolean by preferences.delegates.boolean( - key = KEY_DESIGN_DARK_THEME, - default = true, - onChanged = ::onPreferencesChanged, - ) - - override var designColorToken: String by preferences.delegates.string( - key = KEY_DESIGN_COLOR_TOKEN, - default = "${ColorToken.MAUVE}", - onChanged = ::onPreferencesChanged, - ) - - override var designDarkThemeToken: String by preferences.delegates.string( - key = KEY_DESIGN_DARK_TOKEN, - default = "${DarkThemeToken.FRAPPE}", - onChanged = ::onPreferencesChanged, - ) - override var backgroundGeneration: Boolean by preferences.delegates.boolean( - key = KEY_BACKGROUND_GENERATION, - onChanged = ::onPreferencesChanged, - ) - - override var backgroundProcessCount: Int by preferences.delegates.int( - key = KEY_BACKGROUND_PROCESS_COUNT, - default = 0, - ) - - override var galleryGrid: Grid by preferences.delegates.complexInt( - key = KEY_GALLERY_GRID, - default = Grid.entries.first(), - serialize = { grid -> grid.ordinal }, - deserialize = { index -> Grid.entries[index] }, - onChanged = ::onPreferencesChanged, - ) - - override fun observe(): Flowable = preferencesChangedSubject - .toFlowable(BackpressureStrategy.LATEST) - .map { - Settings( - serverUrl = automatic1111ServerUrl, - sdModel = sdModel, - demoMode = demoMode, - developerMode = developerMode, - localDiffusionAllowCancel = localOnnxAllowCancel, - localDiffusionSchedulerThread = localOnnxSchedulerThread, - monitorConnectivity = monitorConnectivity, - backgroundGeneration = backgroundGeneration, - autoSaveAiResults = autoSaveAiResults, - saveToMediaStore = saveToMediaStore, - formAdvancedOptionsAlwaysShow = formAdvancedOptionsAlwaysShow, - formPromptTaggedInput = formPromptTaggedInput, - source = source, - hordeApiKey = hordeApiKey, - localUseNNAPI = localOnnxUseNNAPI, - designUseSystemColorPalette = designUseSystemColorPalette, - designUseSystemDarkTheme = designUseSystemDarkTheme, - designDarkTheme = designDarkTheme, - designColorToken = designColorToken, - designDarkThemeToken = designDarkThemeToken, - galleryGrid = galleryGrid, - ) - } - - override fun refresh(): Completable = Completable.fromAction { - preferencesChangedSubject.onNext(Unit) - } - - private fun onPreferencesChanged(value: T) = preferencesChangedSubject.onNext(value) - - companion object { - const val KEY_SERVER_URL = "key_server_url" - const val KEY_SWARM_SERVER_URL = "key_swarm_server_url" - const val KEY_SWARM_MODEL = "key_swarm_model" - const val KEY_DEMO_MODE = "key_demo_mode" - const val KEY_DEVELOPER_MODE = "key_developer_mode" - const val KEY_LOCAL_DIFFUSION_CUSTOM_MODEL_PATH = "key_local_diffusion_custom_model_path" - const val KEY_MEDIA_PIPE_CUSTOM_MODEL_PATH = "key_mediapipe_custom_model_path" - const val KEY_ALLOW_LOCAL_DIFFUSION_CANCEL = "key_allow_local_diffusion_cancel" - const val KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD = "key_local_diffusion_scheduler_thread" - const val KEY_MONITOR_CONNECTIVITY = "key_monitor_connection" - const val KEY_AI_AUTO_SAVE = "key_ai_auto_save" - const val KEY_SAVE_TO_MEDIA_STORE = "key_save_to_media_store" - const val KEY_FORM_ALWAYS_SHOW_ADVANCED_OPTIONS = "key_always_show_advanced_options" - const val KEY_FORM_PROMPT_TAGGED_INPUT = "key_prompt_tagged_input_kb" - const val KEY_SERVER_SOURCE = "key_server_source" - const val KEY_SD_MODEL = "key_sd_model" - const val KEY_HORDE_API_KEY = "key_horde_api_key" - const val KEY_OPEN_AI_API_KEY = "key_open_ai_api_key" - const val KEY_HUGGING_FACE_API_KEY = "key_hugging_face_api_key" - const val KEY_HUGGING_FACE_MODEL_KEY = "key_hugging_face_model_key" - const val KEY_STABILITY_AI_API_KEY = "key_stability_ai_api_key" - const val KEY_STABILITY_AI_ENGINE_ID_KEY = "key_stability_ai_engine_id_key" - const val KEY_ON_BOARDING_COMPLETE = "key_on_boarding_complete" - const val KEY_FORCE_SETUP_AFTER_UPDATE = "force_upd_setup_v0.x.x-v0.6.2" - const val KEY_MEDIA_PIPE_MODEL_ID = "key_mediapipe_model_id" - const val KEY_LOCAL_MODEL_ID = "key_local_model_id" - const val KEY_LOCAL_NN_API = "key_local_nn_api" - const val KEY_DESIGN_DYNAMIC_COLORS = "key_design_dynamic_colors" - const val KEY_DESIGN_SYSTEM_DARK_THEME = "key_design_system_dark_theme" - const val KEY_DESIGN_DARK_THEME = "key_design_dark_theme" - const val KEY_DESIGN_COLOR_TOKEN = "key_design_color_token_theme" - const val KEY_DESIGN_DARK_TOKEN = "key_design_dark_color_token_theme" - const val KEY_BACKGROUND_GENERATION = "key_background_generation" - const val KEY_BACKGROUND_PROCESS_COUNT = "key_background_process_count" - const val KEY_GALLERY_GRID = "key_gallery_grid" - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/preference/SessionPreferenceImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/preference/SessionPreferenceImpl.kt deleted file mode 100644 index 00be950de..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/preference/SessionPreferenceImpl.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.data.preference - -import com.shifthackz.aisdv1.domain.preference.SessionPreference - -class SessionPreferenceImpl : SessionPreference { - - private var _swarmUiSessionId: String = "" - - override var swarmUiSessionId: String - get() = _swarmUiSessionId - set(value) { - _swarmUiSessionId = value - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/DownloadableModelRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/DownloadableModelRemoteDataSource.kt deleted file mode 100644 index 1a5525345..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/DownloadableModelRemoteDataSource.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.file.unzip -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.network.api.sdai.DownloadableModelsApi -import com.shifthackz.aisdv1.network.response.DownloadableModelResponse -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single -import java.io.File - -internal class DownloadableModelRemoteDataSource( - private val api: DownloadableModelsApi, - private val fileProviderDescriptor: FileProviderDescriptor, -) : DownloadableModelDataSource.Remote { - - override fun fetch(): Single> = Single.zip( - api - .fetchOnnxModels() - .map { it.mapRawToCheckpointDomain(LocalAiModel.Type.ONNX) }, - api - .fetchMediaPipeModels() - .map { it.mapRawToCheckpointDomain(LocalAiModel.Type.MediaPipe) }, - ::Pair, - ) - .map { (onnx, mediapipe) -> listOf(onnx, mediapipe).flatten() } - - override fun download(id: String, url: String): Observable = Completable - .fromAction { - val dir = File("${fileProviderDescriptor.localModelDirPath}/${id}") - val destination = File(getDestinationPath(id)) - if (destination.exists()) destination.delete() - if (!dir.exists()) dir.mkdirs() - } - .andThen( - api.downloadModel( - remoteUrl = url, - localPath = getDestinationPath(id), - stateProgress = DownloadState::Downloading, - stateComplete = DownloadState::Complete, - stateFailed = DownloadState::Error, - ) - ) - .flatMap { state -> - val chain = Observable.just(state) - if (state is DownloadState.Complete) { - Completable - .create { emitter -> - try { - state.file.unzip() - emitter.onComplete() - } catch (e: Exception) { - emitter.onError(e) - } - } - .andThen(Completable.fromAction { File(getDestinationPath(id)).delete() }) - .andThen(chain) - } else { - chain - } - } - - private fun getDestinationPath(id: String): String { - return "${fileProviderDescriptor.localModelDirPath}/${id}/model.zip" - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/HuggingFaceGenerationRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/HuggingFaceGenerationRemoteDataSource.kt deleted file mode 100644 index cd0488e84..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/HuggingFaceGenerationRemoteDataSource.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.mappers.mapCloudToAiGenResult -import com.shifthackz.aisdv1.data.mappers.mapToHuggingFaceRequest -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.network.api.huggingface.HuggingFaceApi -import com.shifthackz.aisdv1.network.api.huggingface.HuggingFaceInferenceApi -import io.reactivex.rxjava3.core.Single - -internal class HuggingFaceGenerationRemoteDataSource( - private val huggingFaceApi: HuggingFaceApi, - private val huggingFaceInferenceApi: HuggingFaceInferenceApi, - private val converter: BitmapToBase64Converter, -) : HuggingFaceGenerationDataSource.Remote { - - override fun validateApiKey(): Single = huggingFaceApi - .validateBearerToken() - .andThen(Single.just(true)) - .onErrorResumeNext { t -> - errorLog(t) - Single.just(false) - } - - override fun textToImage( - modelName: String, - payload: TextToImagePayload, - ): Single = huggingFaceInferenceApi - .generate(modelName, payload.mapToHuggingFaceRequest()) - .map(BitmapToBase64Converter::Input) - .flatMap(converter::invoke) - .map(BitmapToBase64Converter.Output::base64ImageString) - .map { base64 -> payload to base64 } - .map(Pair::mapCloudToAiGenResult) - - override fun imageToImage( - modelName: String, - payload: ImageToImagePayload, - ): Single = huggingFaceInferenceApi - .generate(modelName, payload.mapToHuggingFaceRequest()) - .map(BitmapToBase64Converter::Input) - .flatMap(converter::invoke) - .map(BitmapToBase64Converter.Output::base64ImageString) - .map { base64 -> payload to base64 } - .map(Pair::mapCloudToAiGenResult) - -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/HuggingFaceModelsRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/HuggingFaceModelsRemoteDataSource.kt deleted file mode 100644 index 73056260e..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/HuggingFaceModelsRemoteDataSource.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceModelsDataSource -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import com.shifthackz.aisdv1.network.api.sdai.HuggingFaceModelsApi -import com.shifthackz.aisdv1.network.model.HuggingFaceModelRaw - -internal class HuggingFaceModelsRemoteDataSource( - private val api: HuggingFaceModelsApi, -) : HuggingFaceModelsDataSource.Remote { - - override fun fetchHuggingFaceModels() = api - .fetchHuggingFaceModels() - .map(List::mapRawToCheckpointDomain) - .onErrorReturn { listOf(HuggingFaceModel.default) } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/OpenAiGenerationRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/OpenAiGenerationRemoteDataSource.kt deleted file mode 100644 index 0f4f1e375..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/OpenAiGenerationRemoteDataSource.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.data.mappers.mapCloudToAiGenResult -import com.shifthackz.aisdv1.data.mappers.mapToOpenAiRequest -import com.shifthackz.aisdv1.domain.datasource.OpenAiGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.network.api.openai.OpenAiApi -import io.reactivex.rxjava3.core.Single -import java.lang.IllegalStateException - -internal class OpenAiGenerationRemoteDataSource( - private val api: OpenAiApi, -) : OpenAiGenerationDataSource.Remote { - - override fun validateApiKey() = api - .validateBearerToken() - .andThen(Single.just(true)) - .onErrorResumeNext { t -> - errorLog(t) - Single.just(false) - } - - override fun textToImage(payload: TextToImagePayload) = payload - .mapToOpenAiRequest() - .let(api::generateImage) - .flatMap { response -> - response.data?.firstOrNull()?.b64json?.let { base64 -> - Single.just(payload to base64) - } ?: run { - Single.error(IllegalStateException("Got null data object from API.")) - } - } - .map(Pair::mapCloudToAiGenResult) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/RandomImageRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/RandomImageRemoteDataSource.kt deleted file mode 100644 index 14ed16b84..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/RandomImageRemoteDataSource.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.domain.datasource.RandomImageDataSource -import com.shifthackz.aisdv1.network.api.imagecdn.ImageCdnRestApi - -internal class RandomImageRemoteDataSource( - private val api: ImageCdnRestApi, -) : RandomImageDataSource.Remote { - - override fun fetch() = api.fetchRandomImage() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/ReportRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/ReportRemoteDataSource.kt deleted file mode 100644 index a7ec462dc..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/ReportRemoteDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.domain.datasource.ReportDataSource -import com.shifthackz.aisdv1.domain.entity.ReportReason -import com.shifthackz.aisdv1.network.api.sdai.ReportApi -import com.shifthackz.aisdv1.network.request.ReportRequest -import io.reactivex.rxjava3.core.Completable - -internal class ReportRemoteDataSource(private val api: ReportApi) : ReportDataSource.Remote { - - override fun send( - text: String, - reason: ReportReason, - image: String, - source: String, - model: String - ): Completable { - val payload = ReportRequest(text, reason.toString(), image, source, model) - return api.postReport(payload) - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/ServerConfigurationRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/ServerConfigurationRemoteDataSource.kt deleted file mode 100755 index 42e1d8c99..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/ServerConfigurationRemoteDataSource.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapToDomain -import com.shifthackz.aisdv1.data.mappers.mapToRequest -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.ServerConfigurationDataSource -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_SD_OPTIONS -import com.shifthackz.aisdv1.network.model.ServerConfigurationRaw - -internal class ServerConfigurationRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: Automatic1111RestApi, -) : ServerConfigurationDataSource.Remote { - - override fun fetchConfiguration() = serverUrlProvider(PATH_SD_OPTIONS) - .flatMap(api::fetchConfiguration) - .map(ServerConfigurationRaw::mapToDomain) - - override fun updateConfiguration(configuration: ServerConfiguration) = - serverUrlProvider(PATH_SD_OPTIONS) - .flatMapCompletable { url -> - api.updateConfiguration(url, configuration.mapToRequest()) - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiCreditsRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiCreditsRemoteDataSource.kt deleted file mode 100644 index a7e6f30ad..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiCreditsRemoteDataSource.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource -import com.shifthackz.aisdv1.network.api.stabilityai.StabilityAiApi -import io.reactivex.rxjava3.core.Single - -internal class StabilityAiCreditsRemoteDataSource( - private val api: StabilityAiApi, -) : StabilityAiCreditsDataSource.Remote { - - override fun fetch(): Single = api - .fetchCredits() - .map { it.credits ?: 0f } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiEnginesRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiEnginesRemoteDataSource.kt deleted file mode 100644 index b6d681219..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiEnginesRemoteDataSource.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.domain.datasource.StabilityAiEnginesDataSource -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine -import com.shifthackz.aisdv1.network.api.stabilityai.StabilityAiApi -import com.shifthackz.aisdv1.network.model.StabilityAiEngineRaw -import io.reactivex.rxjava3.core.Single - -internal class StabilityAiEnginesRemoteDataSource( - private val api: StabilityAiApi, -) : StabilityAiEnginesDataSource.Remote { - - override fun fetch(): Single> = api - .fetchEngines() - .map(List::mapRawToCheckpointDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionEmbeddingsRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionEmbeddingsRemoteDataSource.kt deleted file mode 100644 index ae45d9621..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionEmbeddingsRemoteDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.EmbeddingsDataSource -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_EMBEDDINGS -import com.shifthackz.aisdv1.network.response.SdEmbeddingsResponse - -internal class StableDiffusionEmbeddingsRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: Automatic1111RestApi, -) : EmbeddingsDataSource.Remote.Automatic1111 { - - override fun fetchEmbeddings() = serverUrlProvider(PATH_EMBEDDINGS) - .flatMap(api::fetchEmbeddings) - .map(SdEmbeddingsResponse::mapRawToCheckpointDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionGenerationRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionGenerationRemoteDataSource.kt deleted file mode 100755 index 9da06ca23..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionGenerationRemoteDataSource.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.core.common.extensions.fixUrlSlashes -import com.shifthackz.aisdv1.data.mappers.mapToAiGenResult -import com.shifthackz.aisdv1.data.mappers.mapToRequest -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_IMG_TO_IMG -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_TXT_TO_IMG -import com.shifthackz.aisdv1.network.response.SdGenerationResponse - -internal class StableDiffusionGenerationRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: Automatic1111RestApi, -) : StableDiffusionGenerationDataSource.Remote { - - override fun checkAvailability() = serverUrlProvider("/") - .flatMapCompletable(api::healthCheck) - - override fun checkAvailability(url: String) = api.healthCheck(url.fixUrlSlashes()) - - override fun textToImage(payload: TextToImagePayload) = serverUrlProvider(PATH_TXT_TO_IMG) - .flatMap { url -> api.textToImage(url, payload.mapToRequest()) } - .map { response -> payload to response } - .map(Pair::mapToAiGenResult) - - override fun imageToImage(payload: ImageToImagePayload) = serverUrlProvider(PATH_IMG_TO_IMG) - .flatMap { url -> api.imageToImage(url, payload.mapToRequest()) } - .map { response -> payload to response } - .map(Pair::mapToAiGenResult) - - override fun interruptGeneration() = serverUrlProvider(Automatic1111RestApi.PATH_INTERRUPT) - .flatMapCompletable(api::interrupt) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionHyperNetworksRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionHyperNetworksRemoteDataSource.kt deleted file mode 100644 index 059d8dd75..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionHyperNetworksRemoteDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionHyperNetworksDataSource -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_HYPER_NETWORKS -import com.shifthackz.aisdv1.network.model.StableDiffusionHyperNetworkRaw - -internal class StableDiffusionHyperNetworksRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: Automatic1111RestApi, -) : StableDiffusionHyperNetworksDataSource.Remote { - - override fun fetchHyperNetworks() = serverUrlProvider(PATH_HYPER_NETWORKS) - .flatMap(api::fetchHyperNetworks) - .map(List::mapRawToCheckpointDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionLorasRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionLorasRemoteDataSource.kt deleted file mode 100644 index dd0d546d9..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionLorasRemoteDataSource.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapToDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.LorasDataSource -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_LORAS -import com.shifthackz.aisdv1.network.model.StableDiffusionLoraRaw -import io.reactivex.rxjava3.core.Single - -internal class StableDiffusionLorasRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: Automatic1111RestApi, -) : LorasDataSource.Remote.Automatic1111 { - - override fun fetchLoras(): Single> = serverUrlProvider(PATH_LORAS) - .flatMap(api::fetchLoras) - .map(List::mapToDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionModelsRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionModelsRemoteDataSource.kt deleted file mode 100755 index 464858578..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionModelsRemoteDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionModelsDataSource -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_SD_MODELS -import com.shifthackz.aisdv1.network.model.StableDiffusionModelRaw - -internal class StableDiffusionModelsRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: Automatic1111RestApi, -) : StableDiffusionModelsDataSource.Remote { - - override fun fetchSdModels() = serverUrlProvider(PATH_SD_MODELS) - .flatMap(api::fetchSdModels) - .map(List::mapRawToCheckpointDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionSamplersRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionSamplersRemoteDataSource.kt deleted file mode 100755 index 44056fa77..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StableDiffusionSamplersRemoteDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionSamplersDataSource -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_SAMPLERS -import com.shifthackz.aisdv1.network.model.StableDiffusionSamplerRaw - -internal class StableDiffusionSamplersRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: Automatic1111RestApi, -) : StableDiffusionSamplersDataSource.Remote { - - override fun fetchSamplers() = serverUrlProvider(PATH_SAMPLERS) - .flatMap(api::fetchSamplers) - .map(List::mapRawToCheckpointDomain) -} \ No newline at end of file diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SupportersRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/SupportersRemoteDataSource.kt deleted file mode 100644 index ee8efe19a..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SupportersRemoteDataSource.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToDomain -import com.shifthackz.aisdv1.domain.datasource.SupportersDataSource -import com.shifthackz.aisdv1.domain.entity.Supporter -import com.shifthackz.aisdv1.network.api.sdai.DonateApi -import com.shifthackz.aisdv1.network.model.SupporterRaw -import io.reactivex.rxjava3.core.Single - -internal class SupportersRemoteDataSource( - private val api: DonateApi, -) : SupportersDataSource.Remote { - - override fun fetch(): Single> = api - .fetchSupporters() - .map(List::mapRawToDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiEmbeddingsRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiEmbeddingsRemoteDataSource.kt deleted file mode 100644 index 6909ff538..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiEmbeddingsRemoteDataSource.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToEmbeddingDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.EmbeddingsDataSource -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi.Companion.PATH_MODELS -import com.shifthackz.aisdv1.network.request.SwarmUiModelsRequest -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse -import io.reactivex.rxjava3.core.Single - -class SwarmUiEmbeddingsRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: SwarmUiApi, -) : EmbeddingsDataSource.Remote.SwarmUi { - - override fun fetchEmbeddings(sessionId: String): Single> = serverUrlProvider(PATH_MODELS) - .flatMap { url -> - val request = SwarmUiModelsRequest( - sessionId = sessionId, - subType = "Embedding", - path = "", - depth = 3, - ) - api.fetchModels(url, request) - } - .map(SwarmUiModelsResponse::mapRawToEmbeddingDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiGenerationRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiGenerationRemoteDataSource.kt deleted file mode 100644 index 958be2e36..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiGenerationRemoteDataSource.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.core.common.extensions.fixUrlSlashes -import com.shifthackz.aisdv1.core.imageprocessing.Base64EncodingConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.mappers.mapCloudToAiGenResult -import com.shifthackz.aisdv1.data.mappers.mapToSwarmUiRequest -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.SwarmUiGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi.Companion.PATH_GENERATE -import com.shifthackz.aisdv1.network.request.SwarmUiGenerationRequest -import io.reactivex.rxjava3.core.Single - -class SwarmUiGenerationRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: SwarmUiApi, - private val bmpToBase64Converter: BitmapToBase64Converter, - private val base64EncodingConverter: Base64EncodingConverter, -) : SwarmUiGenerationDataSource.Remote { - - override fun textToImage( - sessionId: String, - model: String, - payload: TextToImagePayload - ): Single = - generate( - payload = payload, - request = payload.mapToSwarmUiRequest(sessionId, model), - ) - .map(Pair::mapCloudToAiGenResult) - - override fun imageToImage( - sessionId: String, - model: String, - payload: ImageToImagePayload, - ): Single = payload - .base64Image - .let(Base64EncodingConverter::Input) - .let(base64EncodingConverter::invoke) - .map(Base64EncodingConverter.Output::base64) - .map { base64 -> "data:image/png;base64,${base64}" } - .map { base64Uri -> payload.copy(base64Image = base64Uri) } - .flatMap { encodedPayload -> - generate( - payload = encodedPayload, - request = encodedPayload.mapToSwarmUiRequest(sessionId, model), - ) - } - .map { (_, outBase64) -> payload to outBase64 } - .map(Pair::mapCloudToAiGenResult) - - private fun generate( - payload: T, - request: SwarmUiGenerationRequest, - ): Single> = serverUrlProvider(PATH_GENERATE) - .flatMap { url -> api.generate(url, request) } - .flatMap { response -> - serverUrlProvider("").map { url -> response to url } - } - .flatMap { (response, url) -> - response.images - ?.firstOrNull() - ?.let { endpoint -> Single.just("$url/$endpoint".fixUrlSlashes()) } - ?: Single.error(IllegalStateException("Bad response")) - } - .flatMap(api::downloadImage) - .map(BitmapToBase64Converter::Input) - .flatMap(bmpToBase64Converter::invoke) - .map(BitmapToBase64Converter.Output::base64ImageString) - .map { base64 -> payload to base64 } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiLorasRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiLorasRemoteDataSource.kt deleted file mode 100644 index 0b6b14df3..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiLorasRemoteDataSource.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToLoraDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.LorasDataSource -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi.Companion.PATH_MODELS -import com.shifthackz.aisdv1.network.request.SwarmUiModelsRequest -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse -import io.reactivex.rxjava3.core.Single - -internal class SwarmUiLorasRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: SwarmUiApi, -) : LorasDataSource.Remote.SwarmUi { - - override fun fetchLoras(sessionId: String): Single> = serverUrlProvider(PATH_MODELS) - .flatMap { url -> - val request = SwarmUiModelsRequest( - sessionId = sessionId, - subType = "LoRA", - path = "", - depth = 3, - ) - api.fetchModels(url, request) - } - .map(SwarmUiModelsResponse::mapRawToLoraDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiModelsRemoteDataSource.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiModelsRemoteDataSource.kt deleted file mode 100644 index c2e57c04d..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiModelsRemoteDataSource.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.SwarmUiModelsDataSource -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi.Companion.PATH_MODELS -import com.shifthackz.aisdv1.network.request.SwarmUiModelsRequest -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse -import io.reactivex.rxjava3.core.Single - -internal class SwarmUiModelsRemoteDataSource( - private val serverUrlProvider: ServerUrlProvider, - private val api: SwarmUiApi, -) : SwarmUiModelsDataSource.Remote { - - override fun fetchSwarmModels(sessionId: String): Single> = PATH_MODELS - .let(serverUrlProvider::invoke) - .flatMap { url -> - val request = SwarmUiModelsRequest( - sessionId = sessionId, - subType = "Stable-Diffusion", - path = "", - depth = 3, - ) - api.fetchModels(url, request) - } - .map(SwarmUiModelsResponse::mapRawToCheckpointDomain) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiSessionDataSourceImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiSessionDataSourceImpl.kt deleted file mode 100644 index 8c191c306..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/SwarmUiSessionDataSourceImpl.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.core.common.extensions.fixUrlSlashes -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.preference.SessionPreference -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi.Companion.PATH_SESSION -import com.shifthackz.aisdv1.network.exception.BadSessionException -import io.reactivex.rxjava3.core.Single - -internal class SwarmUiSessionDataSourceImpl( - private val api: SwarmUiApi, - private val sessionPreference: SessionPreference, - private val serverUrlProvider: ServerUrlProvider, -) : SwarmUiSessionDataSource { - - override fun getSessionId(connectUrl: String?): Single = - if (sessionPreference.swarmUiSessionId.isBlank()) { - forceRenew(connectUrl) - } else { - Single.just(sessionPreference.swarmUiSessionId) - } - - override fun forceRenew(connectUrl: String?): Single { - val chain = connectUrl - ?.let { url -> "$url/$PATH_SESSION".fixUrlSlashes() } - ?.let(api::getNewSession) - ?: serverUrlProvider(PATH_SESSION).flatMap(api::getNewSession) - - return chain - .flatMap { response -> - response.sessionId - ?.takeIf(String::isNotBlank) - ?.let { Single.just(it) } - ?: Single.error(IllegalStateException("Bad session ID.")) - } - .map { sessionId -> - sessionPreference.swarmUiSessionId = sessionId - sessionId - } - } - - override fun handleSessionError(chain: Single): Single = chain.onErrorResumeNext { t -> - if (t is BadSessionException) { - forceRenew().flatMap { chain } - } else { - Single.error(t) - } - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImpl.kt deleted file mode 100644 index 0e46f816c..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository -import io.reactivex.rxjava3.core.Single - -internal class DownloadableModelRepositoryImpl( - private val remoteDataSource: DownloadableModelDataSource.Remote, - private val localDataSource: DownloadableModelDataSource.Local, - private val buildInfoProvider: BuildInfoProvider, -) : DownloadableModelRepository { - - override fun download(id: String, url: String) = remoteDataSource.download(id, url) - - override fun delete(id: String) = localDataSource.delete(id) - - override fun getAllOnnx() = remoteDataSource - .fetch() - .flatMapCompletable(localDataSource::save) - .andThen(localDataSource.getAllOnnx()) - .onErrorResumeNext { localDataSource.getAllOnnx() } - - override fun getAllMediaPipe(): Single> { - if (buildInfoProvider.type == BuildType.FOSS) { - return Single.just(emptyList()) - } - return remoteDataSource - .fetch() - .flatMapCompletable(localDataSource::save) - .andThen(localDataSource.getAllMediaPipe()) - .onErrorResumeNext { localDataSource.getAllMediaPipe() } - } - - override fun observeAllOnnx() = localDataSource.observeAllOnnx() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/EmbeddingsRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/EmbeddingsRepositoryImpl.kt deleted file mode 100644 index d29315387..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/EmbeddingsRepositoryImpl.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.EmbeddingsDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.EmbeddingsRepository -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class EmbeddingsRepositoryImpl( - private val rdsA1111: EmbeddingsDataSource.Remote.Automatic1111, - private val rdsSwarm: EmbeddingsDataSource.Remote.SwarmUi, - private val swarmSession: SwarmUiSessionDataSource, - private val lds: EmbeddingsDataSource.Local, - private val preferenceManager: PreferenceManager, -) : EmbeddingsRepository { - - override fun fetchEmbeddings(): Completable = when (preferenceManager.source) { - ServerSource.AUTOMATIC1111 -> rdsA1111 - .fetchEmbeddings() - .flatMapCompletable(lds::insertEmbeddings) - - ServerSource.SWARM_UI -> swarmSession - .getSessionId() - .flatMap(rdsSwarm::fetchEmbeddings) - .let(swarmSession::handleSessionError) - .flatMapCompletable(lds::insertEmbeddings) - - else -> Completable.complete() - } - - override fun fetchAndGetEmbeddings(): Single> = fetchEmbeddings() - .onErrorComplete() - .andThen(lds.getEmbeddings()) - - override fun getEmbeddings(): Single> = lds.getEmbeddings() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImpl.kt deleted file mode 100644 index c4e8614a7..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.core.CoreMediaStoreRepository -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository -import io.reactivex.rxjava3.core.Single - -internal class GenerationResultRepositoryImpl( - preferenceManager: PreferenceManager, - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - private val localDataSource: GenerationResultDataSource.Local, -) : CoreMediaStoreRepository( - preferenceManager, - mediaStoreGateway, - base64ToBitmapConverter, -), GenerationResultRepository { - - override fun getAll() = localDataSource.queryAll() - - override fun getPage(limit: Int, offset: Int) = localDataSource.queryPage(limit, offset) - - override fun getMediaStoreInfo() = getInfo() - - override fun getById(id: Long) = localDataSource.queryById(id) - - override fun getByIds(idList: List) = localDataSource.queryByIdList(idList) - - override fun insert(result: AiGenerationResult) = localDataSource - .insert(result) - .flatMap { id -> exportToMediaStore(result).andThen(Single.just(id)) } - - override fun deleteById(id: Long) = localDataSource.deleteById(id) - - override fun deleteByIdList(idList: List) = localDataSource.deleteByIdList(idList) - - override fun deleteAll() = localDataSource.deleteAll() - - override fun toggleVisibility(id: Long): Single = localDataSource - .queryById(id) - .map { it.copy(hidden = !it.hidden) } - .flatMap(localDataSource::insert) - .flatMap { localDataSource.queryById(id) } - .map(AiGenerationResult::hidden) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/HordeGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/HordeGenerationRepositoryImpl.kt deleted file mode 100644 index d69792f2c..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/HordeGenerationRepositoryImpl.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.HordeGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository - -internal class HordeGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - preferenceManager: PreferenceManager, - backgroundWorkObserver: BackgroundWorkObserver, - private val remoteDataSource: HordeGenerationDataSource.Remote, - private val statusSource: HordeGenerationDataSource.StatusSource, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), HordeGenerationRepository { - - override fun observeStatus() = statusSource.observe() - - override fun validateApiKey() = remoteDataSource.validateApiKey() - - override fun generateFromText(payload: TextToImagePayload) = remoteDataSource - .textToImage(payload) - .flatMap(::insertGenerationResult) - - override fun generateFromImage(payload: ImageToImagePayload) = remoteDataSource - .imageToImage(payload) - .flatMap(::insertGenerationResult) - - override fun interruptGeneration() = remoteDataSource.interruptGeneration() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/HuggingFaceGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/HuggingFaceGenerationRepositoryImpl.kt deleted file mode 100644 index e30af6110..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/HuggingFaceGenerationRepositoryImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository - -internal class HuggingFaceGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - backgroundWorkObserver: BackgroundWorkObserver, - private val preferenceManager: PreferenceManager, - private val remoteDataSource: HuggingFaceGenerationDataSource.Remote, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), HuggingFaceGenerationRepository { - - override fun validateApiKey() = remoteDataSource.validateApiKey() - - override fun generateFromText(payload: TextToImagePayload) = remoteDataSource - .textToImage(preferenceManager.huggingFaceModel, payload) - .flatMap(::insertGenerationResult) - - override fun generateFromImage(payload: ImageToImagePayload) = remoteDataSource - .imageToImage(preferenceManager.huggingFaceModel, payload) - .flatMap(::insertGenerationResult) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/HuggingFaceModelsRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/HuggingFaceModelsRepositoryImpl.kt deleted file mode 100644 index abf2e578e..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/HuggingFaceModelsRepositoryImpl.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceModelsDataSource -import com.shifthackz.aisdv1.domain.repository.HuggingFaceModelsRepository - -internal class HuggingFaceModelsRepositoryImpl( - private val remoteDataSource: HuggingFaceModelsDataSource.Remote, - private val localDataSource: HuggingFaceModelsDataSource.Local, -) : HuggingFaceModelsRepository { - - override fun fetchHuggingFaceModels() = remoteDataSource - .fetchHuggingFaceModels() - .concatMapCompletable(localDataSource::save) - - override fun fetchAndGetHuggingFaceModels() = fetchHuggingFaceModels() - .onErrorComplete() - .andThen(getHuggingFaceModels()) - - override fun getHuggingFaceModels() = localDataSource.getAll() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt deleted file mode 100644 index 625a1ca83..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.data.mappers.mapLocalDiffusionToAiGenResult -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository -import io.reactivex.rxjava3.core.Single - -internal class LocalDiffusionGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - backgroundWorkObserver: BackgroundWorkObserver, - private val preferenceManager: PreferenceManager, - private val localDiffusion: LocalDiffusion, - private val downloadableLocalDataSource: DownloadableModelDataSource.Local, - private val bitmapToBase64Converter: BitmapToBase64Converter, - private val schedulersProvider: SchedulersProvider, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), LocalDiffusionGenerationRepository { - - override fun observeStatus() = localDiffusion.observeStatus() - - override fun generateFromText(payload: TextToImagePayload) = downloadableLocalDataSource - .getSelectedOnnx() - .flatMap { model -> - if (model.downloaded) generate(payload) - else Single.error(IllegalStateException("Model not downloaded.")) - } - - override fun interruptGeneration() = localDiffusion.interrupt() - - private fun generate(payload: TextToImagePayload) = localDiffusion - .process(payload) - .subscribeOn(schedulersProvider.byToken(preferenceManager.localOnnxSchedulerThread)) - .map(BitmapToBase64Converter::Input) - .flatMap(bitmapToBase64Converter::invoke) - .map(BitmapToBase64Converter.Output::base64ImageString) - .map { base64 -> payload to base64 } - .map(Pair::mapLocalDiffusionToAiGenResult) - .flatMap(::insertGenerationResult) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LorasRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/LorasRepositoryImpl.kt deleted file mode 100644 index 745657e22..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LorasRepositoryImpl.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.LorasDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.LorasRepository -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class LorasRepositoryImpl( - private val rdsA1111: LorasDataSource.Remote.Automatic1111, - private val rdsSwarm: LorasDataSource.Remote.SwarmUi, - private val swarmSession: SwarmUiSessionDataSource, - private val lds: LorasDataSource.Local, - private val preferenceManager: PreferenceManager, -) : LorasRepository { - - override fun fetchLoras(): Completable = when (preferenceManager.source) { - ServerSource.AUTOMATIC1111 -> rdsA1111 - .fetchLoras() - .flatMapCompletable(lds::insertLoras) - - ServerSource.SWARM_UI -> swarmSession - .getSessionId() - .flatMap(rdsSwarm::fetchLoras) - .let(swarmSession::handleSessionError) - .flatMapCompletable(lds::insertLoras) - - else -> Completable.complete() - } - - override fun fetchAndGetLoras(): Single> = fetchLoras() - .onErrorComplete() - .andThen(getLoras()) - - override fun getLoras(): Single> = lds.getLoras() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/MediaPipeGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/MediaPipeGenerationRepositoryImpl.kt deleted file mode 100644 index 884712091..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/MediaPipeGenerationRepositoryImpl.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.data.mappers.mapLocalDiffusionToAiGenResult -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.mediapipe.MediaPipe -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.MediaPipeGenerationRepository -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.schedulers.Schedulers - -internal class MediaPipeGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - backgroundWorkObserver: BackgroundWorkObserver, - preferenceManager: PreferenceManager, - private val schedulersProvider: SchedulersProvider, - private val mediaPipe: MediaPipe, - private val bitmapToBase64Converter: BitmapToBase64Converter, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), MediaPipeGenerationRepository { - - override fun generateFromText(payload: TextToImagePayload): Single = mediaPipe - .process(payload) - .subscribeOn(schedulersProvider.singleThread.let(Schedulers::from)) - .map(BitmapToBase64Converter::Input) - .flatMap(bitmapToBase64Converter::invoke) - .map(BitmapToBase64Converter.Output::base64ImageString) - .map { base64 -> payload to base64 } - .map(Pair::mapLocalDiffusionToAiGenResult) - .flatMap(::insertGenerationResult) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/OpenAiGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/OpenAiGenerationRepositoryImpl.kt deleted file mode 100644 index 3b6b85784..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/OpenAiGenerationRepositoryImpl.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.OpenAiGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.OpenAiGenerationRepository - -internal class OpenAiGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - preferenceManager: PreferenceManager, - backgroundWorkObserver: BackgroundWorkObserver, - private val remoteDataSource: OpenAiGenerationDataSource.Remote, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), OpenAiGenerationRepository { - - override fun validateApiKey() = remoteDataSource.validateApiKey() - - override fun generateFromText(payload: TextToImagePayload) = remoteDataSource - .textToImage(payload) - .flatMap(::insertGenerationResult) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/RandomImageRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/RandomImageRepositoryImpl.kt deleted file mode 100644 index c734ddb97..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/RandomImageRepositoryImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.RandomImageDataSource -import com.shifthackz.aisdv1.domain.repository.RandomImageRepository - -internal class RandomImageRepositoryImpl( - private val remoteDataSource: RandomImageDataSource.Remote, -) : RandomImageRepository { - - override fun fetchAndGet() = remoteDataSource.fetch() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/ReportRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/ReportRepositoryImpl.kt deleted file mode 100644 index 0abfd08bb..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/ReportRepositoryImpl.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.ReportDataSource -import com.shifthackz.aisdv1.domain.entity.ReportReason -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.ReportRepository -import io.reactivex.rxjava3.core.Completable - -internal class ReportRepositoryImpl( - private val rds: ReportDataSource.Remote, - private val preferenceManager: PreferenceManager, -) : ReportRepository { - - override fun send(text: String, reason: ReportReason, image: String): Completable { - val source = preferenceManager.source - val model = when (source) { - ServerSource.HUGGING_FACE -> preferenceManager.huggingFaceModel - ServerSource.STABILITY_AI -> preferenceManager.stabilityAiEngineId - ServerSource.LOCAL_MICROSOFT_ONNX -> preferenceManager.localOnnxModelId - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> preferenceManager.localMediaPipeModelId - else -> "" - } - return rds.send(text, reason, image, source.toString(), model) - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/ServerConfigurationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/ServerConfigurationRepositoryImpl.kt deleted file mode 100755 index f7c205673..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/ServerConfigurationRepositoryImpl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.ServerConfigurationDataSource -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository - -internal class ServerConfigurationRepositoryImpl( - private val remoteDataSource: ServerConfigurationDataSource.Remote, - private val localDataSource: ServerConfigurationDataSource.Local, -) : ServerConfigurationRepository { - - override fun fetchConfiguration() = remoteDataSource - .fetchConfiguration() - .flatMapCompletable(localDataSource::save) - - override fun fetchAndGetConfiguration() = fetchConfiguration() - .onErrorComplete() - .andThen(getConfiguration()) - - override fun getConfiguration() = localDataSource.get() - - override fun updateConfiguration(configuration: ServerConfiguration) = remoteDataSource - .updateConfiguration(configuration) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiEnginesRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiEnginesRepositoryImpl.kt deleted file mode 100644 index 551661168..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiEnginesRepositoryImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.StabilityAiEnginesDataSource -import com.shifthackz.aisdv1.domain.repository.StabilityAiEnginesRepository - -internal class StabilityAiEnginesRepositoryImpl( - private val remoteDataSource: StabilityAiEnginesDataSource.Remote, -) : StabilityAiEnginesRepository { - - override fun fetchAndGet() = remoteDataSource.fetch() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiGenerationRepositoryImpl.kt deleted file mode 100644 index ba8908ca6..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiGenerationRepositoryImpl.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository -import io.reactivex.rxjava3.core.Single -import java.io.ByteArrayOutputStream - -internal class StabilityAiGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - backgroundWorkObserver: BackgroundWorkObserver, - private val base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - private val preferenceManager: PreferenceManager, - private val generationRds: StabilityAiGenerationDataSource.Remote, - private val creditsRds: StabilityAiCreditsDataSource.Remote, - private val creditsLds: StabilityAiCreditsDataSource.Local, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), StabilityAiGenerationRepository { - - override fun validateApiKey() = generationRds.validateApiKey() - - override fun generateFromText(payload: TextToImagePayload) = generationRds - .textToImage(preferenceManager.stabilityAiEngineId, payload) - .flatMap(::insertGenerationResult) - .flatMap(::refreshCredits) - - override fun generateFromImage(payload: ImageToImagePayload) = payload - .base64Image - .let(Base64ToBitmapConverter::Input) - .let(base64ToBitmapConverter::invoke) - .map(Base64ToBitmapConverter.Output::bitmap) - .map { bmp -> - val stream = ByteArrayOutputStream() - bmp.compress(Bitmap.CompressFormat.PNG, 100, stream) - stream.toByteArray() - } - .flatMap { bytes -> - generationRds.imageToImage( - engineId = preferenceManager.stabilityAiEngineId, - payload = payload, - imageBytes = bytes, - ) - } - .flatMap(::insertGenerationResult) - .flatMap(::refreshCredits) - - - private fun refreshCredits(ai: AiGenerationResult) = creditsRds - .fetch() - .flatMapCompletable(creditsLds::save) - .onErrorComplete() - .andThen(Single.just(ai)) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionGenerationRepositoryImpl.kt deleted file mode 100755 index 340657d5d..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionGenerationRepositoryImpl.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionGenerationDataSource -import com.shifthackz.aisdv1.domain.demo.ImageToImageDemo -import com.shifthackz.aisdv1.domain.demo.TextToImageDemo -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import io.reactivex.rxjava3.core.Single - -internal class StableDiffusionGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - backgroundWorkObserver: BackgroundWorkObserver, - base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - private val remoteDataSource: StableDiffusionGenerationDataSource.Remote, - private val preferenceManager: PreferenceManager, - private val textToImageDemo: TextToImageDemo, - private val imageToImageDemo: ImageToImageDemo, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), StableDiffusionGenerationRepository { - - override fun checkApiAvailability() = remoteDataSource.checkAvailability() - - override fun checkApiAvailability(url: String) = remoteDataSource.checkAvailability(url) - - override fun generateFromText(payload: TextToImagePayload): Single { - val chain = - if (preferenceManager.demoMode) textToImageDemo.getDemoBase64(payload) - else remoteDataSource.textToImage(payload) - - return chain.flatMap(::insertGenerationResult) - } - - override fun generateFromImage(payload: ImageToImagePayload): Single { - val chain = - if (preferenceManager.demoMode) imageToImageDemo.getDemoBase64(payload) - else remoteDataSource.imageToImage(payload) - - return chain.flatMap(::insertGenerationResult) - } - - override fun interruptGeneration() = remoteDataSource.interruptGeneration() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionHyperNetworksRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionHyperNetworksRepositoryImpl.kt deleted file mode 100644 index de4992949..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionHyperNetworksRepositoryImpl.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionHyperNetworksDataSource -import com.shifthackz.aisdv1.domain.repository.StableDiffusionHyperNetworksRepository - -internal class StableDiffusionHyperNetworksRepositoryImpl( - private val remoteDataSource: StableDiffusionHyperNetworksDataSource.Remote, - private val localDataSource: StableDiffusionHyperNetworksDataSource.Local, -) : StableDiffusionHyperNetworksRepository { - - override fun fetchHyperNetworks() = remoteDataSource - .fetchHyperNetworks() - .flatMapCompletable(localDataSource::insertHyperNetworks) - - override fun fetchAndGetHyperNetworks() = fetchHyperNetworks() - .onErrorComplete() - .andThen(getHyperNetworks()) - - override fun getHyperNetworks() = localDataSource.getHyperNetworks() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionModelsRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionModelsRepositoryImpl.kt deleted file mode 100755 index 8f79d4620..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionModelsRepositoryImpl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionModelsDataSource -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel -import com.shifthackz.aisdv1.domain.repository.StableDiffusionModelsRepository -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class StableDiffusionModelsRepositoryImpl( - private val remoteDataSource: StableDiffusionModelsDataSource.Remote, - private val localDataSource: StableDiffusionModelsDataSource.Local, -) : StableDiffusionModelsRepository { - - override fun fetchModels(): Completable = remoteDataSource - .fetchSdModels() - .flatMapCompletable(localDataSource::insertModels) - - override fun fetchAndGetModels(): Single> = fetchModels() - .onErrorComplete() - .andThen(getModels()) - - override fun getModels(): Single> = - localDataSource.getModels() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionSamplersRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionSamplersRepositoryImpl.kt deleted file mode 100755 index aace29997..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StableDiffusionSamplersRepositoryImpl.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionSamplersDataSource -import com.shifthackz.aisdv1.domain.repository.StableDiffusionSamplersRepository - -internal class StableDiffusionSamplersRepositoryImpl( - private val remoteDataSource: StableDiffusionSamplersDataSource.Remote, - private val localDataSource: StableDiffusionSamplersDataSource.Local, -) : StableDiffusionSamplersRepository { - - override fun fetchSamplers() = remoteDataSource - .fetchSamplers() - .flatMapCompletable(localDataSource::insertSamplers) - - override fun getSamplers() = localDataSource.getSamplers() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/SupportersRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/SupportersRepositoryImpl.kt deleted file mode 100644 index d9abd5d68..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/SupportersRepositoryImpl.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.SupportersDataSource -import com.shifthackz.aisdv1.domain.entity.Supporter -import com.shifthackz.aisdv1.domain.repository.SupportersRepository -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class SupportersRepositoryImpl( - private val rds: SupportersDataSource.Remote, - private val lds: SupportersDataSource.Local, -) : SupportersRepository { - - override fun fetchSupporters(): Completable = rds - .fetch() - .flatMapCompletable(lds::save) - - override fun fetchAndGetSupporters(): Single> = fetchSupporters() - .onErrorComplete() - .andThen(getSupporters()) - - override fun getSupporters(): Single> = lds.getAll() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/SwarmUiGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/SwarmUiGenerationRepositoryImpl.kt deleted file mode 100644 index 8dc845df4..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/SwarmUiGenerationRepositoryImpl.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.core.CoreGenerationRepository -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.SwarmUiGenerationRepository -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class SwarmUiGenerationRepositoryImpl( - mediaStoreGateway: MediaStoreGateway, - base64ToBitmapConverter: Base64ToBitmapConverter, - localDataSource: GenerationResultDataSource.Local, - backgroundWorkObserver: BackgroundWorkObserver, - private val preferenceManager: PreferenceManager, - private val session: SwarmUiSessionDataSource, - private val remoteDataSource: SwarmUiGenerationDataSource.Remote, -) : CoreGenerationRepository( - mediaStoreGateway = mediaStoreGateway, - base64ToBitmapConverter = base64ToBitmapConverter, - localDataSource = localDataSource, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, -), SwarmUiGenerationRepository { - - override fun checkApiAvailability(): Completable = session - .getSessionId() - .ignoreElement() - - override fun checkApiAvailability(url: String): Completable = session - .getSessionId(url) - .ignoreElement() - - override fun generateFromText(payload: TextToImagePayload): Single = session - .getSessionId() - .flatMap { sessionId -> - remoteDataSource.textToImage( - sessionId = sessionId, - model = preferenceManager.swarmUiModel, - payload = payload, - ) - } - .let(session::handleSessionError) - .flatMap(::insertGenerationResult) - - override fun generateFromImage(payload: ImageToImagePayload): Single = session - .getSessionId() - .flatMap { sessionId -> - remoteDataSource.imageToImage( - sessionId = sessionId, - model = preferenceManager.swarmUiModel, - payload = payload, - ) - } - .let(session::handleSessionError) - .flatMap(::insertGenerationResult) -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/SwarmUiModelsRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/SwarmUiModelsRepositoryImpl.kt deleted file mode 100644 index e2d6ca4cd..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/SwarmUiModelsRepositoryImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.datasource.SwarmUiModelsDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import com.shifthackz.aisdv1.domain.repository.SwarmUiModelsRepository -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class SwarmUiModelsRepositoryImpl( - private val session: SwarmUiSessionDataSource, - private val rds: SwarmUiModelsDataSource.Remote, - private val lds: SwarmUiModelsDataSource.Local, -) : SwarmUiModelsRepository { - - override fun fetchModels(): Completable = session - .getSessionId() - .flatMap(rds::fetchSwarmModels) - .let(session::handleSessionError) - .flatMapCompletable(lds::insertModels) - - override fun fetchAndGetModels(): Single> = fetchModels() - .onErrorComplete() - .andThen(getModels()) - - override fun getModels(): Single> = lds.getModels() -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/TemporaryGenerationResultRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/TemporaryGenerationResultRepositoryImpl.kt deleted file mode 100644 index e53acfa75..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/TemporaryGenerationResultRepositoryImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.repository.TemporaryGenerationResultRepository -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -internal class TemporaryGenerationResultRepositoryImpl : TemporaryGenerationResultRepository { - - private var lastCachedResult: AiGenerationResult? = null - - override fun put(result: AiGenerationResult) = Completable.fromAction { - lastCachedResult = result - } - - override fun get(): Single { - return lastCachedResult - ?.let { Single.just(it) } - ?: Single.error(IllegalStateException("No last cached result.")) - } -} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/WakeLockRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/WakeLockRepositoryImpl.kt deleted file mode 100644 index 902637071..000000000 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/WakeLockRepositoryImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import android.os.PowerManager -import com.shifthackz.aisdv1.domain.repository.WakeLockRepository - -internal class WakeLockRepositoryImpl( - val powerManager: () -> PowerManager, -) : WakeLockRepository { - - private var _wakeLock: PowerManager.WakeLock? = null - override val wakeLock: PowerManager.WakeLock - get() = _wakeLock ?: run { - val wl = powerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG) - _wakeLock = wl - wl - } - - companion object { - private const val TAG = "SDAI:WakeLock" - } -} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/core/CoreGenerationRepository.kt b/data/src/main/java/dev/minios/pdaiv1/data/core/CoreGenerationRepository.kt new file mode 100644 index 000000000..578fbe6f9 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/core/CoreGenerationRepository.kt @@ -0,0 +1,88 @@ +package dev.minios.pdaiv1.data.core + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.MediaType +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.reactivex.rxjava3.core.Single + +internal abstract class CoreGenerationRepository( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + private val localDataSource: GenerationResultDataSource.Local, + private val preferenceManager: PreferenceManager, + private val backgroundWorkObserver: BackgroundWorkObserver, + private val mediaFileManager: MediaFileManager, + private val blurHashEncoder: BlurHashEncoder, +) : CoreMediaStoreRepository(preferenceManager, mediaStoreGateway, base64ToBitmapConverter) { + + protected fun insertGenerationResult(ai: AiGenerationResult): Single { + if (backgroundWorkObserver.hasActiveTasks() || preferenceManager.autoSaveAiResults) { + val converted = ai.saveMediaToFiles() + return localDataSource + .insert(converted) + .flatMap { id -> exportToMediaStore(ai).andThen(Single.just(ai.copy(id))) } + .doOnSuccess { backgroundWorkObserver.postNewImageSignal() } + } + return Single.just(ai) + } + + /** + * Converts base64 data to files before saving to database. + * This prevents SQLiteBlobTooBigException for large images. + * Also generates BlurHash for gallery placeholders. + */ + private fun AiGenerationResult.saveMediaToFiles(): AiGenerationResult { + var mediaPath = this.mediaPath + var inputMediaPath = this.inputMediaPath + var blurHash = this.blurHash + + // Convert main image base64 to file and generate BlurHash + if (image.isNotEmpty() && !mediaFileManager.isFilePath(image) && !mediaFileManager.isVideoUrl(image)) { + mediaPath = mediaFileManager.migrateBase64ToFile(image, mediaType) + // Generate BlurHash for gallery placeholder + if (blurHash.isEmpty()) { + blurHash = generateBlurHash(image) + } + } + + // Convert input image base64 to file + if (inputImage.isNotEmpty() && !mediaFileManager.isFilePath(inputImage)) { + inputMediaPath = mediaFileManager.migrateBase64ToFile(inputImage, MediaType.IMAGE) + } + + return copy( + image = "", // Clear base64 from database + inputImage = "", // Clear base64 from database + mediaPath = mediaPath, + inputMediaPath = inputMediaPath, + blurHash = blurHash, + ) + } + + /** + * Generates BlurHash from base64 image string. + * Returns empty string on failure. + */ + private fun generateBlurHash(base64Image: String): String { + return try { + val bytes = android.util.Base64.decode(base64Image, android.util.Base64.DEFAULT) + val bitmap = android.graphics.BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + if (bitmap != null) { + // Scale down for faster encoding + val scaledBitmap = android.graphics.Bitmap.createScaledBitmap(bitmap, 32, 32, true) + val hash = blurHashEncoder.encodeSync(scaledBitmap) + if (scaledBitmap != bitmap) scaledBitmap.recycle() + bitmap.recycle() + hash + } else "" + } catch (e: Exception) { + "" + } + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/core/CoreMediaStoreRepository.kt b/data/src/main/java/dev/minios/pdaiv1/data/core/CoreMediaStoreRepository.kt new file mode 100644 index 000000000..6608d678f --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/core/CoreMediaStoreRepository.kt @@ -0,0 +1,50 @@ +package dev.minios.pdaiv1.data.core + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import java.io.ByteArrayOutputStream + +internal abstract class CoreMediaStoreRepository( + private val preferenceManager: PreferenceManager, + private val mediaStoreGateway: MediaStoreGateway, + private val base64ToBitmapConverter: Base64ToBitmapConverter, +) { + + protected fun exportToMediaStore(result: AiGenerationResult): Completable { + if (!preferenceManager.saveToMediaStore) return Completable.complete() + // Skip export if there's no image data (e.g., FalAi uses mediaPath directly) + if (result.image.isEmpty()) return Completable.complete() + return export(result) + } + + protected fun getInfo(): Single = Single.create { emitter -> + emitter.onSuccess(mediaStoreGateway.getInfo()) + } + + private fun export(result: AiGenerationResult) = result.image + .let(Base64ToBitmapConverter::Input) + .let(base64ToBitmapConverter::invoke) + .map(Base64ToBitmapConverter.Output::bitmap) + .flatMapCompletable(::processBitmap) + + private fun processBitmap(bmp: Bitmap) = Completable + .fromAction { + val stream = ByteArrayOutputStream() + bmp.compress(Bitmap.CompressFormat.PNG, 100, stream) + mediaStoreGateway.exportToFile( + fileName = "pdai_${System.currentTimeMillis()}", + content = stream.toByteArray(), + ) + } + .onErrorComplete { t -> + errorLog(t) + true + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/di/DataModule.kt b/data/src/main/java/dev/minios/pdaiv1/data/di/DataModule.kt new file mode 100755 index 000000000..627c94237 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/di/DataModule.kt @@ -0,0 +1,4 @@ +package dev.minios.pdaiv1.data.di + +val dataModule = (remoteDataSourceModule + localDataSourceModule + repositoryModule) + .toTypedArray() diff --git a/data/src/main/java/dev/minios/pdaiv1/data/di/LocalDataSourceModule.kt b/data/src/main/java/dev/minios/pdaiv1/data/di/LocalDataSourceModule.kt new file mode 100755 index 000000000..381afd73e --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/di/LocalDataSourceModule.kt @@ -0,0 +1,60 @@ +package dev.minios.pdaiv1.data.di + +import dev.minios.pdaiv1.data.feature.MediaFileManagerImpl +import dev.minios.pdaiv1.data.gateway.DatabaseClearGatewayImpl +import dev.minios.pdaiv1.data.gateway.mediastore.MediaStoreGatewayFactory +import dev.minios.pdaiv1.data.local.DownloadableModelLocalDataSource +import dev.minios.pdaiv1.data.local.EmbeddingsLocalDataSource +import dev.minios.pdaiv1.data.local.FalAiEndpointBuiltInDataSource +import dev.minios.pdaiv1.data.local.FalAiEndpointLocalDataSource +import dev.minios.pdaiv1.data.local.GenerationResultLocalDataSource +import dev.minios.pdaiv1.data.local.HuggingFaceModelsLocalDataSource +import dev.minios.pdaiv1.data.local.LorasLocalDataSource +import dev.minios.pdaiv1.data.local.ServerConfigurationLocalDataSource +import dev.minios.pdaiv1.data.local.StabilityAiCreditsLocalDataSource +import dev.minios.pdaiv1.data.local.StableDiffusionHyperNetworksLocalDataSource +import dev.minios.pdaiv1.data.local.StableDiffusionModelsLocalDataSource +import dev.minios.pdaiv1.data.local.StableDiffusionSamplersLocalDataSource +import dev.minios.pdaiv1.data.local.SupportersLocalDataSource +import dev.minios.pdaiv1.data.local.SwarmUiModelsLocalDataSource +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.datasource.EmbeddingsDataSource +import dev.minios.pdaiv1.domain.datasource.FalAiEndpointDataSource +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.HuggingFaceModelsDataSource +import dev.minios.pdaiv1.domain.datasource.LorasDataSource +import dev.minios.pdaiv1.domain.datasource.ServerConfigurationDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionHyperNetworksDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionModelsDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionSamplersDataSource +import dev.minios.pdaiv1.domain.datasource.SupportersDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiModelsDataSource +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.gateway.DatabaseClearGateway +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val localDataSourceModule = module { + singleOf(::DatabaseClearGatewayImpl) bind DatabaseClearGateway::class + single { MediaFileManagerImpl(androidContext(), get()) } + // !!! Do not use [factoryOf] for StabilityAiCreditsLocalDataSource, it has default constructor + single { StabilityAiCreditsLocalDataSource() } + factoryOf(::StableDiffusionModelsLocalDataSource) bind StableDiffusionModelsDataSource.Local::class + factoryOf(::StableDiffusionSamplersLocalDataSource) bind StableDiffusionSamplersDataSource.Local::class + factoryOf(::LorasLocalDataSource) bind LorasDataSource.Local::class + factoryOf(::StableDiffusionHyperNetworksLocalDataSource) bind StableDiffusionHyperNetworksDataSource.Local::class + factoryOf(::EmbeddingsLocalDataSource) bind EmbeddingsDataSource.Local::class + factoryOf(::SwarmUiModelsLocalDataSource) bind SwarmUiModelsDataSource.Local::class + factoryOf(::ServerConfigurationLocalDataSource) bind ServerConfigurationDataSource.Local::class + factoryOf(::GenerationResultLocalDataSource) bind GenerationResultDataSource.Local::class + factoryOf(::DownloadableModelLocalDataSource) bind DownloadableModelDataSource.Local::class + factoryOf(::HuggingFaceModelsLocalDataSource) bind HuggingFaceModelsDataSource.Local::class + factoryOf(::SupportersLocalDataSource) bind SupportersDataSource.Local::class + factoryOf(::FalAiEndpointLocalDataSource) bind FalAiEndpointDataSource.Local::class + factoryOf(::FalAiEndpointBuiltInDataSource) bind FalAiEndpointDataSource.BuiltIn::class + factory { MediaStoreGatewayFactory(androidContext(), get()).invoke() } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/di/RemoteDataSourceModule.kt b/data/src/main/java/dev/minios/pdaiv1/data/di/RemoteDataSourceModule.kt new file mode 100755 index 000000000..a002832e1 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/di/RemoteDataSourceModule.kt @@ -0,0 +1,115 @@ +package dev.minios.pdaiv1.data.di + +import dev.minios.pdaiv1.core.common.extensions.fixUrlSlashes +import dev.minios.pdaiv1.data.gateway.ServerConnectivityGatewayImpl +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.data.remote.DownloadableModelRemoteDataSource +import dev.minios.pdaiv1.data.remote.FalAiEndpointRemoteDataSource +import dev.minios.pdaiv1.data.remote.FalAiGenerationRemoteDataSource +import dev.minios.pdaiv1.data.remote.HordeGenerationRemoteDataSource +import dev.minios.pdaiv1.data.remote.HordeStatusSource +import dev.minios.pdaiv1.data.remote.HuggingFaceGenerationRemoteDataSource +import dev.minios.pdaiv1.data.remote.HuggingFaceModelsRemoteDataSource +import dev.minios.pdaiv1.data.remote.OpenAiGenerationRemoteDataSource +import dev.minios.pdaiv1.data.remote.RandomImageRemoteDataSource +import dev.minios.pdaiv1.data.remote.ReportRemoteDataSource +import dev.minios.pdaiv1.data.remote.ServerConfigurationRemoteDataSource +import dev.minios.pdaiv1.data.remote.StabilityAiCreditsRemoteDataSource +import dev.minios.pdaiv1.data.remote.StabilityAiEnginesRemoteDataSource +import dev.minios.pdaiv1.data.remote.StabilityAiGenerationRemoteDataSource +import dev.minios.pdaiv1.data.remote.StableDiffusionEmbeddingsRemoteDataSource +import dev.minios.pdaiv1.data.remote.StableDiffusionGenerationRemoteDataSource +import dev.minios.pdaiv1.data.remote.StableDiffusionHyperNetworksRemoteDataSource +import dev.minios.pdaiv1.data.remote.StableDiffusionLorasRemoteDataSource +import dev.minios.pdaiv1.data.remote.StableDiffusionModelsRemoteDataSource +import dev.minios.pdaiv1.data.remote.StableDiffusionSamplersRemoteDataSource +import dev.minios.pdaiv1.data.remote.SupportersRemoteDataSource +import dev.minios.pdaiv1.data.remote.SwarmUiEmbeddingsRemoteDataSource +import dev.minios.pdaiv1.data.remote.SwarmUiGenerationRemoteDataSource +import dev.minios.pdaiv1.data.remote.SwarmUiLorasRemoteDataSource +import dev.minios.pdaiv1.data.remote.SwarmUiModelsRemoteDataSource +import dev.minios.pdaiv1.data.remote.SwarmUiSessionDataSourceImpl +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.datasource.EmbeddingsDataSource +import dev.minios.pdaiv1.domain.datasource.FalAiEndpointDataSource +import dev.minios.pdaiv1.domain.datasource.FalAiGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.HordeGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.HuggingFaceGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.HuggingFaceModelsDataSource +import dev.minios.pdaiv1.domain.datasource.LorasDataSource +import dev.minios.pdaiv1.domain.datasource.OpenAiGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.RandomImageDataSource +import dev.minios.pdaiv1.domain.datasource.ReportDataSource +import dev.minios.pdaiv1.domain.datasource.ServerConfigurationDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiEnginesDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionHyperNetworksDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionModelsDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionSamplersDataSource +import dev.minios.pdaiv1.domain.datasource.SupportersDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiModelsDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.gateway.ServerConnectivityGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.network.connectivity.ConnectivityMonitor +import io.reactivex.rxjava3.core.Single +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.core.parameter.parametersOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val remoteDataSourceModule = module { + single { + ServerUrlProvider { endpoint -> + val prefs = get() + val chain = if (prefs.source == ServerSource.SWARM_UI) { + Single.fromCallable(prefs::swarmUiServerUrl) + } else { + Single.fromCallable(prefs::automatic1111ServerUrl) + } + chain + .map(String::fixUrlSlashes) + .map { baseUrl -> "$baseUrl/$endpoint" } + } + } + singleOf(::HordeStatusSource) bind HordeGenerationDataSource.StatusSource::class + factoryOf(::HordeGenerationRemoteDataSource) bind HordeGenerationDataSource.Remote::class + factoryOf(::HuggingFaceGenerationRemoteDataSource) bind HuggingFaceGenerationDataSource.Remote::class + factoryOf(::OpenAiGenerationRemoteDataSource) bind OpenAiGenerationDataSource.Remote::class + factoryOf(::SwarmUiSessionDataSourceImpl) bind SwarmUiSessionDataSource::class + factoryOf(::SwarmUiGenerationRemoteDataSource) bind SwarmUiGenerationDataSource.Remote::class + factoryOf(::SwarmUiModelsRemoteDataSource) bind SwarmUiModelsDataSource.Remote::class + factoryOf(::SwarmUiLorasRemoteDataSource) bind LorasDataSource.Remote.SwarmUi::class + factoryOf(::SwarmUiEmbeddingsRemoteDataSource) bind EmbeddingsDataSource.Remote.SwarmUi::class + factoryOf(::StableDiffusionGenerationRemoteDataSource) bind StableDiffusionGenerationDataSource.Remote::class + factoryOf(::StableDiffusionSamplersRemoteDataSource) bind StableDiffusionSamplersDataSource.Remote::class + factoryOf(::StableDiffusionModelsRemoteDataSource) bind StableDiffusionModelsDataSource.Remote::class + factoryOf(::StableDiffusionLorasRemoteDataSource) bind LorasDataSource.Remote.Automatic1111::class + factoryOf(::StableDiffusionHyperNetworksRemoteDataSource) bind StableDiffusionHyperNetworksDataSource.Remote::class + factoryOf(::StableDiffusionEmbeddingsRemoteDataSource) bind EmbeddingsDataSource.Remote.Automatic1111::class + factoryOf(::ServerConfigurationRemoteDataSource) bind ServerConfigurationDataSource.Remote::class + factoryOf(::RandomImageRemoteDataSource) bind RandomImageDataSource.Remote::class + factoryOf(::DownloadableModelRemoteDataSource) bind DownloadableModelDataSource.Remote::class + factoryOf(::SupportersRemoteDataSource) bind SupportersDataSource.Remote::class + factoryOf(::HuggingFaceModelsRemoteDataSource) bind HuggingFaceModelsDataSource.Remote::class + factoryOf(::StabilityAiGenerationRemoteDataSource) bind StabilityAiGenerationDataSource.Remote::class + factoryOf(::StabilityAiCreditsRemoteDataSource) bind StabilityAiCreditsDataSource.Remote::class + factoryOf(::StabilityAiEnginesRemoteDataSource) bind StabilityAiEnginesDataSource.Remote::class + factoryOf(::FalAiGenerationRemoteDataSource) bind FalAiGenerationDataSource.Remote::class + factoryOf(::FalAiEndpointRemoteDataSource) bind FalAiEndpointDataSource.Remote::class + factoryOf(::ReportRemoteDataSource) bind ReportDataSource.Remote::class + + factory { + val lambda: () -> Boolean = { + val prefs = get() + prefs.source != ServerSource.AUTOMATIC1111 && prefs.source != ServerSource.SWARM_UI + } + val monitor = get { parametersOf(lambda) } + ServerConnectivityGatewayImpl(monitor, get()) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/di/RepositoryModule.kt b/data/src/main/java/dev/minios/pdaiv1/data/di/RepositoryModule.kt new file mode 100755 index 000000000..4a4822126 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/di/RepositoryModule.kt @@ -0,0 +1,104 @@ +package dev.minios.pdaiv1.data.di + +import android.content.Context +import android.os.PowerManager +import dev.minios.pdaiv1.data.repository.DownloadableModelRepositoryImpl +import dev.minios.pdaiv1.data.repository.EmbeddingsRepositoryImpl +import dev.minios.pdaiv1.data.repository.FalAiEndpointRepositoryImpl +import dev.minios.pdaiv1.data.repository.FalAiGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.ForgeModulesRepositoryImpl +import dev.minios.pdaiv1.data.repository.GenerationResultRepositoryImpl +import dev.minios.pdaiv1.data.repository.HordeGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.HuggingFaceGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.HuggingFaceModelsRepositoryImpl +import dev.minios.pdaiv1.data.repository.LocalDiffusionGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.LorasRepositoryImpl +import dev.minios.pdaiv1.data.repository.MediaPipeGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.OpenAiGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.QnnGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.RandomImageRepositoryImpl +import dev.minios.pdaiv1.data.repository.ReportRepositoryImpl +import dev.minios.pdaiv1.data.repository.ServerConfigurationRepositoryImpl +import dev.minios.pdaiv1.data.repository.StabilityAiCreditsRepositoryImpl +import dev.minios.pdaiv1.data.repository.StabilityAiEnginesRepositoryImpl +import dev.minios.pdaiv1.data.repository.StabilityAiGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.StableDiffusionGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.StableDiffusionHyperNetworksRepositoryImpl +import dev.minios.pdaiv1.data.repository.StableDiffusionModelsRepositoryImpl +import dev.minios.pdaiv1.data.repository.StableDiffusionSamplersRepositoryImpl +import dev.minios.pdaiv1.data.repository.SupportersRepositoryImpl +import dev.minios.pdaiv1.data.repository.SwarmUiGenerationRepositoryImpl +import dev.minios.pdaiv1.data.repository.SwarmUiModelsRepositoryImpl +import dev.minios.pdaiv1.data.repository.TemporaryGenerationResultRepositoryImpl +import dev.minios.pdaiv1.data.repository.WakeLockRepositoryImpl +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository +import dev.minios.pdaiv1.domain.repository.EmbeddingsRepository +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.ForgeModulesRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository +import dev.minios.pdaiv1.domain.repository.HuggingFaceModelsRepository +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.LorasRepository +import dev.minios.pdaiv1.domain.repository.MediaPipeGenerationRepository +import dev.minios.pdaiv1.domain.repository.OpenAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import dev.minios.pdaiv1.domain.repository.RandomImageRepository +import dev.minios.pdaiv1.domain.repository.ReportRepository +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiCreditsRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiEnginesRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionHyperNetworksRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionModelsRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionSamplersRepository +import dev.minios.pdaiv1.domain.repository.SupportersRepository +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository +import dev.minios.pdaiv1.domain.repository.SwarmUiModelsRepository +import dev.minios.pdaiv1.domain.repository.TemporaryGenerationResultRepository +import dev.minios.pdaiv1.domain.repository.WakeLockRepository +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val repositoryModule = module { + single { + WakeLockRepositoryImpl { + androidContext().getSystemService(Context.POWER_SERVICE) as PowerManager + } + } + + singleOf(::TemporaryGenerationResultRepositoryImpl) bind TemporaryGenerationResultRepository::class + factoryOf(::LocalDiffusionGenerationRepositoryImpl) bind LocalDiffusionGenerationRepository::class + factoryOf(::MediaPipeGenerationRepositoryImpl) bind MediaPipeGenerationRepository::class + factoryOf(::QnnGenerationRepositoryImpl) bind QnnGenerationRepository::class + factoryOf(::HordeGenerationRepositoryImpl) bind HordeGenerationRepository::class + factoryOf(::HuggingFaceGenerationRepositoryImpl) bind HuggingFaceGenerationRepository::class + factoryOf(::OpenAiGenerationRepositoryImpl) bind OpenAiGenerationRepository::class + factoryOf(::SwarmUiGenerationRepositoryImpl) bind SwarmUiGenerationRepository::class + factoryOf(::SwarmUiModelsRepositoryImpl) bind SwarmUiModelsRepository::class + factoryOf(::StabilityAiGenerationRepositoryImpl) bind StabilityAiGenerationRepository::class + factoryOf(::StabilityAiCreditsRepositoryImpl) bind StabilityAiCreditsRepository::class + factoryOf(::StabilityAiEnginesRepositoryImpl) bind StabilityAiEnginesRepository::class + factoryOf(::FalAiGenerationRepositoryImpl) bind FalAiGenerationRepository::class + factoryOf(::FalAiEndpointRepositoryImpl) bind FalAiEndpointRepository::class + factoryOf(::StableDiffusionGenerationRepositoryImpl) bind StableDiffusionGenerationRepository::class + factoryOf(::StableDiffusionModelsRepositoryImpl) bind StableDiffusionModelsRepository::class + factoryOf(::StableDiffusionSamplersRepositoryImpl) bind StableDiffusionSamplersRepository::class + factoryOf(::LorasRepositoryImpl) bind LorasRepository::class + factoryOf(::StableDiffusionHyperNetworksRepositoryImpl) bind StableDiffusionHyperNetworksRepository::class + factoryOf(::EmbeddingsRepositoryImpl) bind EmbeddingsRepository::class + factoryOf(::ForgeModulesRepositoryImpl) bind ForgeModulesRepository::class + factoryOf(::ServerConfigurationRepositoryImpl) bind ServerConfigurationRepository::class + factoryOf(::GenerationResultRepositoryImpl) bind GenerationResultRepository::class + factoryOf(::RandomImageRepositoryImpl) bind RandomImageRepository::class + factoryOf(::DownloadableModelRepositoryImpl) bind DownloadableModelRepository::class + factoryOf(::HuggingFaceModelsRepositoryImpl) bind HuggingFaceModelsRepository::class + factoryOf(::SupportersRepositoryImpl) bind SupportersRepository::class + factoryOf(::ReportRepositoryImpl) bind ReportRepository::class +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/feature/MediaFileManagerImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/feature/MediaFileManagerImpl.kt new file mode 100644 index 000000000..8355d6e1e --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/feature/MediaFileManagerImpl.kt @@ -0,0 +1,140 @@ +package dev.minios.pdaiv1.data.feature + +import android.content.Context +import android.util.Base64 +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.entity.MediaType +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.File +import java.io.FileOutputStream +import java.util.UUID + +internal class MediaFileManagerImpl( + private val context: Context, + private val httpClient: OkHttpClient, +) : MediaFileManager { + + private val filesDir: File + get() = context.filesDir + + override fun saveMedia(data: ByteArray, type: MediaType): String { + val dir = File(filesDir, MediaFileManager.MEDIA_DIR) + if (!dir.exists()) dir.mkdirs() + + val fileName = "${UUID.randomUUID()}.${type.extension}" + val file = File(dir, fileName) + + FileOutputStream(file).use { fos -> + fos.write(data) + } + + debugLog("MediaFileManager: Saved media to ${file.absolutePath}") + return "${MediaFileManager.MEDIA_DIR}/$fileName" + } + + override fun saveMediaFromUrl(url: String, type: MediaType): String { + val request = Request.Builder().url(url).build() + val response = httpClient.newCall(request).execute() + + if (!response.isSuccessful) { + throw IllegalStateException("Failed to download media: ${response.code}") + } + + val bytes = response.body?.bytes() + ?: throw IllegalStateException("Empty response body") + + return saveMedia(bytes, type) + } + + override fun saveInputMedia(data: ByteArray, type: MediaType): String { + val dir = File(filesDir, MediaFileManager.INPUT_DIR) + if (!dir.exists()) dir.mkdirs() + + val fileName = "${UUID.randomUUID()}.${type.extension}" + val file = File(dir, fileName) + + FileOutputStream(file).use { fos -> + fos.write(data) + } + + debugLog("MediaFileManager: Saved input media to ${file.absolutePath}") + return "${MediaFileManager.INPUT_DIR}/$fileName" + } + + override fun loadMedia(path: String): ByteArray? { + return try { + val file = getMediaFile(path) + if (file.exists()) { + file.readBytes() + } else { + errorLog("MediaFileManager: File not found: ${file.absolutePath}") + null + } + } catch (e: Exception) { + errorLog(e) + null + } + } + + override fun deleteMedia(path: String): Boolean { + return try { + val file = getMediaFile(path) + if (file.exists()) { + val deleted = file.delete() + debugLog("MediaFileManager: Deleted ${file.absolutePath}: $deleted") + deleted + } else { + true // Already doesn't exist + } + } catch (e: Exception) { + errorLog(e) + false + } + } + + override fun getMediaFile(path: String): File { + return File(filesDir, path) + } + + override fun migrateBase64ToFile(base64: String, type: MediaType): String { + // Handle VIDEO_URL: prefix - for videos, keep the URL format for now + if (isVideoUrl(base64)) { + return base64 + } + + // Skip if already a file path + if (isFilePath(base64)) { + return base64 + } + + // Decode base64 and save to file + return try { + val bytes = Base64.decode(base64, Base64.NO_WRAP) + saveMedia(bytes, type) + } catch (e: Exception) { + errorLog("MediaFileManager: Failed to migrate base64: ${e.message}") + // Return original on error - don't lose data + base64 + } + } + + override fun isFilePath(path: String): Boolean { + return path.startsWith(MediaFileManager.MEDIA_DIR) || + path.startsWith(MediaFileManager.INPUT_DIR) + } + + override fun isVideoUrl(path: String): Boolean { + return path.startsWith(MediaFileManager.VIDEO_URL_PREFIX) + } + + override fun extractVideoUrl(path: String): String? { + return if (isVideoUrl(path)) { + path.removePrefix(MediaFileManager.VIDEO_URL_PREFIX) + } else { + null + } + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/gateway/DatabaseClearGatewayImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/gateway/DatabaseClearGatewayImpl.kt new file mode 100644 index 000000000..93cb89ed5 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/gateway/DatabaseClearGatewayImpl.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.data.gateway + +import dev.minios.pdaiv1.domain.gateway.DatabaseClearGateway +import dev.minios.pdaiv1.storage.gateway.GatewayClearCacheDb +import dev.minios.pdaiv1.storage.gateway.GatewayClearPersistentDb +import io.reactivex.rxjava3.core.Completable + +internal class DatabaseClearGatewayImpl( + private val gatewayClearCacheDb: GatewayClearCacheDb, + private val gatewayClearPersistentDb: GatewayClearPersistentDb, +) : DatabaseClearGateway { + + override fun clearSessionScopeDb(): Completable = Completable.fromAction { + gatewayClearCacheDb() + } + + override fun clearStorageScopeDb(): Completable = Completable.fromAction { + gatewayClearPersistentDb() + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/gateway/ServerConnectivityGatewayImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/gateway/ServerConnectivityGatewayImpl.kt new file mode 100644 index 000000000..9e8771973 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/gateway/ServerConnectivityGatewayImpl.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.data.gateway + +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.gateway.ServerConnectivityGateway +import dev.minios.pdaiv1.network.connectivity.ConnectivityMonitor +import io.reactivex.rxjava3.core.BackpressureStrategy +import io.reactivex.rxjava3.core.Flowable + +internal class ServerConnectivityGatewayImpl( + private val connectivityMonitor: ConnectivityMonitor, + private val serverUrlProvider: ServerUrlProvider, +) : ServerConnectivityGateway { + + override fun observe(): Flowable = serverUrlProvider("") + .flatMapObservable(connectivityMonitor::observe) + .toFlowable(BackpressureStrategy.LATEST) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayFactory.kt b/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayFactory.kt new file mode 100644 index 000000000..f133abc16 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayFactory.kt @@ -0,0 +1,24 @@ +@file:Suppress("DEPRECATION") + +package dev.minios.pdaiv1.data.gateway.mediastore + +import android.content.Context +import dev.minios.pdaiv1.core.common.extensions.shouldUseNewMediaStore +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway + +internal class MediaStoreGatewayFactory( + private val context: Context, + private val fileProviderDescriptor: FileProviderDescriptor, +) { + + operator fun invoke(): MediaStoreGateway { + if (shouldUseNewMediaStore()) { + debugLog("Using Tiramisu and higher implementation for MediaStore") + return MediaStoreGatewayImpl(context, fileProviderDescriptor) + } + debugLog("Using deprecated implementation for MediaStore") + return MediaStoreGatewayOldImpl() + } +} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayImpl.kt similarity index 76% rename from data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayImpl.kt rename to data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayImpl.kt index 4ededa5bb..8f358c38e 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayImpl.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayImpl.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.data.gateway.mediastore +package dev.minios.pdaiv1.data.gateway.mediastore import android.content.ContentUris import android.content.ContentValues @@ -7,10 +7,10 @@ import android.database.Cursor import android.net.Uri import android.os.Environment import android.provider.MediaStore -import com.shifthackz.aisdv1.core.common.extensions.uriFromFile -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.core.common.extensions.uriFromFile +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway import java.io.File /** @@ -30,7 +30,7 @@ internal class MediaStoreGatewayImpl( val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") - put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/SDAI/") + put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/PDAI/") } val extVolumeUri: Uri = MediaStore.Files.getContentUri("external") @@ -80,6 +80,25 @@ internal class MediaStoreGatewayImpl( } } + override fun exportFromFile(fileName: String, sourceFile: File) { + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) + put(MediaStore.MediaColumns.MIME_TYPE, "image/png") + put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/PDAI/") + } + + val extVolumeUri: Uri = MediaStore.Files.getContentUri("external") + val fileUri = context.contentResolver.insert(extVolumeUri, contentValues) + + if (fileUri != null) { + context.contentResolver.openOutputStream(fileUri, "wt")?.use { os -> + sourceFile.inputStream().use { input -> + input.copyTo(os, bufferSize = 8192) + } + } + } + } + override fun getInfo(): MediaStoreInfo { try { val extVolumeUri: Uri = MediaStore.Files.getContentUri("external") diff --git a/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayOldImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayOldImpl.kt new file mode 100644 index 000000000..2b178d20d --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayOldImpl.kt @@ -0,0 +1,57 @@ +package dev.minios.pdaiv1.data.gateway.mediastore + +import android.net.Uri +import android.os.Environment +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import java.io.File + +/** + * Implementation to support old Android versions (12 and lower). + * + * + * Working on: + * - Android 9 API 28 (Emulator) + * - Android 10 API 29 (Emulator) + * - Android 11 API 30 (Emulator) + * - Android 12 API 31 (Emulator) + * + * Not working on: + * - Android 12L API 32 (Emulator) + */ +@Deprecated("Deprecated since Android 12, it is here to support old devices.") +internal class MediaStoreGatewayOldImpl : MediaStoreGateway { + + override fun exportToFile(fileName: String, content: ByteArray) { + val dirPath = Environment.getExternalStorageDirectory().path + DIR_PATH + val dir = File(dirPath) + if (!dir.exists()) dir.mkdirs() + val file = File("${dirPath}/${fileName}.jpg") + if (!file.exists()) file.createNewFile() + file.writeBytes(content) + } + + override fun exportFromFile(fileName: String, sourceFile: File) { + val dirPath = Environment.getExternalStorageDirectory().path + DIR_PATH + val dir = File(dirPath) + if (!dir.exists()) dir.mkdirs() + val destFile = File("${dirPath}/${fileName}.png") + sourceFile.copyTo(destFile, overwrite = true) + } + + override fun getInfo(): MediaStoreInfo { + val dirPath = Environment.getExternalStorageDirectory().path + DIR_PATH + val dir = File(dirPath) + if (dir.exists() && dir.isDirectory) { + return MediaStoreInfo( + count = dir.listFiles()?.size ?: 0, + folderUri = Uri.fromFile(dir), + ) + } + return MediaStoreInfo() + } + + companion object { + private const val DIR_PATH = "/Download/PDAI" + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/DownloadableModelLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/DownloadableModelLocalDataSource.kt new file mode 100644 index 000000000..5879f7b77 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/DownloadableModelLocalDataSource.kt @@ -0,0 +1,171 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.device.DeviceChipsetDetector +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.storage.db.persistent.dao.LocalModelDao +import dev.minios.pdaiv1.storage.db.persistent.entity.LocalModelEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import java.io.File + +internal class DownloadableModelLocalDataSource( + private val fileProviderDescriptor: FileProviderDescriptor, + private val dao: LocalModelDao, + private val preferenceManager: PreferenceManager, + private val buildInfoProvider: BuildInfoProvider, +) : DownloadableModelDataSource.Local { + + private val deviceChipsetSuffix: String by lazy { DeviceChipsetDetector.getChipsetSuffix() } + + override fun getAllOnnx() = dao + .queryByType(LocalAiModel.Type.ONNX.key) + .map(List::mapEntityToDomain) + .map { models -> + buildList { + addAll(models) + if (buildInfoProvider.type != BuildType.PLAY) { + add(LocalAiModel.CustomOnnx) + } + } + } + .flatMap { models -> models.withLocalData() } + + override fun getAllMediaPipe(): Single> = dao + .queryByType(LocalAiModel.Type.MediaPipe.key) + .map(List::mapEntityToDomain) + .map { models -> + buildList { + addAll(models) + if (buildInfoProvider.type != BuildType.PLAY) { + add(LocalAiModel.CustomMediaPipe) + } + } + } + .flatMap { models -> models.withLocalData() } + + override fun getAllQnn(): Single> = dao + .queryByType(LocalAiModel.Type.QNN.key) + .map(List::mapEntityToDomain) + .map { models -> + // Filter models by device chipset compatibility + // Show models that: have no chipset requirement (CPU models) OR match device chipset + models.filter { model -> + model.chipsetSuffix == null || model.chipsetSuffix == deviceChipsetSuffix + } + } + .map { models -> + buildList { + addAll(models) + if (buildInfoProvider.type != BuildType.PLAY) { + add(LocalAiModel.CustomQnn) + } + } + } + .flatMap { models -> models.withLocalData() } + + override fun getById(id: String): Single { + val chain = when (id) { + LocalAiModel.CustomOnnx.id -> Single.just(LocalAiModel.CustomOnnx) + LocalAiModel.CustomMediaPipe.id -> Single.just(LocalAiModel.CustomMediaPipe) + LocalAiModel.CustomQnn.id -> Single.just(LocalAiModel.CustomQnn) + else -> dao + .queryById(id) + .map(LocalModelEntity::mapEntityToDomain) + } + return chain.flatMap { model -> model.withLocalData() } + } + + override fun getSelectedOnnx() = Single + .just(preferenceManager.localOnnxModelId) + .flatMap(::getById) + .onErrorResumeNext { Single.error(IllegalStateException("No selected model.")) } + + override fun observeAllOnnx(): Flowable> = dao + .observeByType(LocalAiModel.Type.ONNX.key) + .map(List::mapEntityToDomain) + .map { models -> + buildList { + addAll(models) + if (buildInfoProvider.type != BuildType.PLAY) add(LocalAiModel.CustomOnnx) + } + } + .flatMap { models -> models.withLocalData().toFlowable() } + + override fun save(list: List) = list + .filter { it.id != LocalAiModel.CustomOnnx.id } + .mapDomainToEntity() + .let(dao::insertList) + + override fun delete(id: String): Completable = Completable.fromAction { + getLocalModelDirectory(id).deleteRecursively() + } + + private fun isDownloaded(model: LocalAiModel) = Single.create { emitter -> + try { + when (model.id) { + // Custom models are never "downloaded" - they use scanned models from custom path + LocalAiModel.CustomOnnx.id, + LocalAiModel.CustomMediaPipe.id, + LocalAiModel.CustomQnn.id -> emitter.onSuccess(false) + + else -> { + + when (model.type) { + LocalAiModel.Type.ONNX -> { + val files = getLocalModelFiles(model.id).filter { it.isDirectory } + emitter.onSuccess(files.size == 4) + } + + LocalAiModel.Type.MediaPipe -> { + val files = getLocalModelFiles(model.id) + emitter.onSuccess(files.isNotEmpty()) + } + + LocalAiModel.Type.QNN -> { + val files = getLocalModelFiles(model.id) + emitter.onSuccess(files.isNotEmpty()) + } + } + } + } + } catch (e: Exception) { + if (!emitter.isDisposed) emitter.onSuccess(false) + } + } + + private fun getLocalModelDirectory(id: String): File { + return File("${fileProviderDescriptor.localModelDirPath}/${id}") + } + + private fun getLocalModelFiles(id: String): List { + val localModelDir = getLocalModelDirectory(id) + if (!localModelDir.exists()) return emptyList() + return localModelDir.listFiles()?.toList() ?: emptyList() + } + + private fun List.withLocalData() = Observable + .fromIterable(this) + .flatMapSingle { model -> model.withLocalData() } + .toList() + + private fun LocalAiModel.withLocalData() = isDownloaded(this) + .map { downloaded -> + copy( + downloaded = downloaded, + selected = when (this.type) { + LocalAiModel.Type.ONNX -> preferenceManager.localOnnxModelId == id + LocalAiModel.Type.MediaPipe -> preferenceManager.localMediaPipeModelId == id + LocalAiModel.Type.QNN -> preferenceManager.localQnnModelId == id + }, + ) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/EmbeddingsLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/EmbeddingsLocalDataSource.kt new file mode 100644 index 000000000..f8a5ae7d0 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/EmbeddingsLocalDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.EmbeddingsDataSource +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionEmbeddingDao +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity + +internal class EmbeddingsLocalDataSource( + private val dao: StableDiffusionEmbeddingDao, +) : EmbeddingsDataSource.Local { + + override fun getEmbeddings() = dao + .queryAll() + .map(List::mapEntityToDomain) + + override fun insertEmbeddings(list: List) = dao + .deleteAll() + .andThen(dao.insertList(list.mapDomainToEntity())) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/FalAiEndpointBuiltInDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/FalAiEndpointBuiltInDataSource.kt new file mode 100644 index 000000000..eda77e893 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/FalAiEndpointBuiltInDataSource.kt @@ -0,0 +1,54 @@ +package dev.minios.pdaiv1.data.local + +import android.content.Context +import dev.minios.pdaiv1.data.mappers.FalAiOpenApiParser +import dev.minios.pdaiv1.domain.datasource.FalAiEndpointDataSource +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import io.reactivex.rxjava3.core.Single + +/** + * Provides built-in fal.ai endpoints from app assets. + * Supports nested folder structure like fal-ai/flux-lora/inpainting.json + */ +internal class FalAiEndpointBuiltInDataSource( + private val context: Context, +) : FalAiEndpointDataSource.BuiltIn { + + override fun getAll(): Single> = Single.fromCallable { + findAllJsonFiles(ASSETS_DIR).mapNotNull { path -> + runCatching { + val json = context.assets + .open(path) + .bufferedReader() + .use { it.readText() } + FalAiOpenApiParser.parse(json, isCustom = false, assetPath = path) + }.getOrNull() + } + } + + /** + * Recursively finds all .json files in the given assets directory. + */ + private fun findAllJsonFiles(dir: String): List { + val result = mutableListOf() + val items = context.assets.list(dir) ?: return emptyList() + + for (item in items) { + val path = "$dir/$item" + if (item.endsWith(".json")) { + result.add(path) + } else { + // Check if it's a directory by trying to list it + val subItems = context.assets.list(path) + if (!subItems.isNullOrEmpty()) { + result.addAll(findAllJsonFiles(path)) + } + } + } + return result + } + + companion object { + private const val ASSETS_DIR = "falai-endpoints" + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/FalAiEndpointLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/FalAiEndpointLocalDataSource.kt new file mode 100644 index 000000000..4d8996af0 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/FalAiEndpointLocalDataSource.kt @@ -0,0 +1,37 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.toDomain +import dev.minios.pdaiv1.data.mappers.toEntity +import dev.minios.pdaiv1.domain.datasource.FalAiEndpointDataSource +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.storage.db.persistent.dao.FalAiEndpointDao +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +/** + * Local storage for custom fal.ai endpoints. + */ +internal class FalAiEndpointLocalDataSource( + private val dao: FalAiEndpointDao, +) : FalAiEndpointDataSource.Local { + + override fun observeAll(): Observable> = dao + .observeAll() + .map { entities -> entities.map { it.toDomain() } } + .toObservable() + + override fun getAll(): Single> = dao + .queryAll() + .map { entities -> entities.map { it.toDomain() } } + + override fun getById(id: String): Single = dao + .queryById(id) + .map { it.toDomain() } + + override fun save(endpoint: FalAiEndpoint): Completable = dao + .insert(endpoint.toEntity()) + + override fun delete(id: String): Completable = dao + .deleteById(id) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/GenerationResultLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/GenerationResultLocalDataSource.kt new file mode 100644 index 000000000..7f8f6ce96 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/GenerationResultLocalDataSource.kt @@ -0,0 +1,61 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ThumbnailData +import dev.minios.pdaiv1.storage.db.persistent.dao.GenerationResultDao +import dev.minios.pdaiv1.storage.db.persistent.entity.GenerationResultEntity +import io.reactivex.rxjava3.core.Single + +internal class GenerationResultLocalDataSource( + private val dao: GenerationResultDao, +) : GenerationResultDataSource.Local { + + override fun insert(result: AiGenerationResult) = result + .mapDomainToEntity() + .let(dao::insert) + + override fun queryAll(): Single> = dao + .query() + .map(List::mapEntityToDomain) + + override fun queryAllIds(): Single> = dao.queryAllIds() + + override fun queryAllIdsWithBlurHash(): Single>> = dao + .queryAllIdsWithBlurHash() + .map { list -> list.map { it.id to it.blurHash } } + + override fun queryThumbnailInfoByIdList(idList: List): Single> = dao + .queryThumbnailInfoByIdList(idList) + .map { list -> list.map { ThumbnailData(it.id, it.mediaPath, it.hidden, it.blurHash) } } + + override fun queryPage(limit: Int, offset: Int) = dao + .queryPage(limit, offset) + .map(List::mapEntityToDomain) + + override fun queryById(id: Long) = dao + .queryById(id) + .map(GenerationResultEntity::mapEntityToDomain) + + override fun queryByIdList(idList: List) = dao + .queryByIdList(idList) + .map(List::mapEntityToDomain) + + override fun deleteById(id: Long) = dao.deleteById(id) + + override fun deleteByIdList(idList: List) = dao.deleteByIdList(idList) + + override fun deleteAll() = dao.deleteAll() + + override fun deleteAllUnliked() = dao.deleteAllUnliked() + + override fun likeByIds(idList: List) = dao.likeByIds(idList) + + override fun unlikeByIds(idList: List) = dao.unlikeByIds(idList) + + override fun hideByIds(idList: List) = dao.hideByIds(idList) + + override fun unhideByIds(idList: List) = dao.unhideByIds(idList) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/HuggingFaceModelsLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/HuggingFaceModelsLocalDataSource.kt new file mode 100644 index 000000000..397c7ff98 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/HuggingFaceModelsLocalDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.HuggingFaceModelsDataSource +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.storage.db.persistent.dao.HuggingFaceModelDao +import dev.minios.pdaiv1.storage.db.persistent.entity.HuggingFaceModelEntity + +internal class HuggingFaceModelsLocalDataSource( + private val dao: HuggingFaceModelDao, +) : HuggingFaceModelsDataSource.Local { + + override fun getAll() = dao + .query() + .map(List::mapEntityToDomain) + + override fun save(models: List) = dao + .deleteAll() + .andThen(dao.insertList(models.mapDomainToEntity())) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/LorasLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/LorasLocalDataSource.kt new file mode 100644 index 000000000..4f07effd1 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/LorasLocalDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.LorasDataSource +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionLoraDao +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionLoraEntity + +internal class LorasLocalDataSource( + private val dao: StableDiffusionLoraDao, +) : LorasDataSource.Local { + + override fun getLoras() = dao + .queryAll() + .map(List::mapEntityToDomain) + + override fun insertLoras(loras: List) = dao + .deleteAll() + .andThen(dao.insertList(loras.mapDomainToEntity())) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/ServerConfigurationLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/ServerConfigurationLocalDataSource.kt new file mode 100755 index 000000000..0ec021255 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/ServerConfigurationLocalDataSource.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapToDomain +import dev.minios.pdaiv1.data.mappers.mapToEntity +import dev.minios.pdaiv1.domain.datasource.ServerConfigurationDataSource +import dev.minios.pdaiv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.storage.db.cache.dao.ServerConfigurationDao +import dev.minios.pdaiv1.storage.db.cache.entity.ServerConfigurationEntity + +internal class ServerConfigurationLocalDataSource( + private val dao: ServerConfigurationDao, +) : ServerConfigurationDataSource.Local { + + override fun save(configuration: ServerConfiguration) = dao + .insert(configuration.mapToEntity()) + + override fun get() = dao + .query() + .map(ServerConfigurationEntity::mapToDomain) +} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/local/StabilityAiCreditsLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/StabilityAiCreditsLocalDataSource.kt similarity index 84% rename from data/src/main/java/com/shifthackz/aisdv1/data/local/StabilityAiCreditsLocalDataSource.kt rename to data/src/main/java/dev/minios/pdaiv1/data/local/StabilityAiCreditsLocalDataSource.kt index 54e65ddfb..b01f3e22d 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/local/StabilityAiCreditsLocalDataSource.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/StabilityAiCreditsLocalDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.subjects.BehaviorSubject diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionHyperNetworksLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionHyperNetworksLocalDataSource.kt new file mode 100644 index 000000000..f5509385b --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionHyperNetworksLocalDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.StableDiffusionHyperNetworksDataSource +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionHyperNetworkDao +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity + +internal class StableDiffusionHyperNetworksLocalDataSource( + private val dao: StableDiffusionHyperNetworkDao, +) : StableDiffusionHyperNetworksDataSource.Local { + + override fun getHyperNetworks() = dao + .queryAll() + .map(List::mapEntityToDomain) + + override fun insertHyperNetworks(list: List) = dao + .deleteAll() + .andThen(dao.insertList(list.mapDomainToEntity())) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionModelsLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionModelsLocalDataSource.kt new file mode 100755 index 000000000..95bf6bfa8 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionModelsLocalDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.StableDiffusionModelsDataSource +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionModelDao +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionModelEntity + +internal class StableDiffusionModelsLocalDataSource( + private val dao: StableDiffusionModelDao, +) : StableDiffusionModelsDataSource.Local { + + override fun getModels() = dao + .queryAll() + .map(List::mapEntityToDomain) + + override fun insertModels(models: List) = dao + .deleteAll() + .andThen(dao.insertList(models.mapDomainToEntity())) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionSamplersLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionSamplersLocalDataSource.kt new file mode 100755 index 000000000..37aa771fc --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/StableDiffusionSamplersLocalDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.StableDiffusionSamplersDataSource +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionSamplerDao +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionSamplerEntity + +internal class StableDiffusionSamplersLocalDataSource( + private val dao: StableDiffusionSamplerDao, +) : StableDiffusionSamplersDataSource.Local { + + override fun getSamplers() = dao + .queryAll() + .map(List::mapEntityToDomain) + + override fun insertSamplers(samplers: List) = dao + .deleteAll() + .andThen(dao.insertList(samplers.mapDomainToEntity())) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/SupportersLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/SupportersLocalDataSource.kt new file mode 100644 index 000000000..f99939754 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/SupportersLocalDataSource.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.SupportersDataSource +import dev.minios.pdaiv1.domain.entity.Supporter +import dev.minios.pdaiv1.storage.db.persistent.dao.SupporterDao +import dev.minios.pdaiv1.storage.db.persistent.entity.SupporterEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class SupportersLocalDataSource( + private val dao: SupporterDao, +) : SupportersDataSource.Local { + + override fun save(data: List): Completable = dao + .deleteAll() + .andThen(dao.insertList(data.mapDomainToEntity())) + + override fun getAll(): Single> = dao + .queryAll() + .map(List::mapEntityToDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/local/SwarmUiModelsLocalDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/local/SwarmUiModelsLocalDataSource.kt new file mode 100644 index 000000000..455daeec9 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/local/SwarmUiModelsLocalDataSource.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mappers.mapDomainToEntity +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.domain.datasource.SwarmUiModelsDataSource +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import dev.minios.pdaiv1.storage.db.cache.dao.SwarmUiModelDao +import dev.minios.pdaiv1.storage.db.cache.entity.SwarmUiModelEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class SwarmUiModelsLocalDataSource( + private val dao: SwarmUiModelDao, +) : SwarmUiModelsDataSource.Local { + + override fun getModels(): Single> = dao + .queryAll() + .map(List::mapEntityToDomain) + + override fun insertModels(models: List): Completable = dao + .deleteAll() + .andThen(dao.insertList(models.mapDomainToEntity())) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/AiGenerationResultMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/AiGenerationResultMappers.kt new file mode 100644 index 000000000..c55a18642 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/AiGenerationResultMappers.kt @@ -0,0 +1,73 @@ +package dev.minios.pdaiv1.data.mappers + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.MediaType +import dev.minios.pdaiv1.storage.db.persistent.entity.GenerationResultEntity + +//region DOMAIN --> ENTITY +fun List.mapDomainToEntity(): List = + map(AiGenerationResult::mapDomainToEntity) + +fun AiGenerationResult.mapDomainToEntity(): GenerationResultEntity = with(this) { + GenerationResultEntity( + id = id, + imageBase64 = image, + originalImageBase64 = inputImage, + createdAt = createdAt, + generationType = type.key, + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + samplingSteps = samplingSteps, + cfgScale = cfgScale, + restoreFaces = restoreFaces, + sampler = sampler, + seed = seed, + subSeed = subSeed, + subSeedStrength = subSeedStrength, + denoisingStrength = denoisingStrength, + hidden = hidden, + liked = liked, + mediaPath = mediaPath, + inputMediaPath = inputMediaPath, + mediaType = mediaType.key, + modelName = modelName, + blurHash = blurHash, + ) +} +//endregion + +//region ENTITY --> DOMAIN +fun List.mapEntityToDomain(): List = + map(GenerationResultEntity::mapEntityToDomain) + +fun GenerationResultEntity.mapEntityToDomain(): AiGenerationResult = with(this) { + AiGenerationResult( + id = id, + image = imageBase64, + inputImage = originalImageBase64, + createdAt = createdAt, + type = AiGenerationResult.Type.parse(generationType), + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + samplingSteps = samplingSteps, + cfgScale = cfgScale, + restoreFaces = restoreFaces, + sampler = sampler, + seed = seed, + subSeed = subSeed, + subSeedStrength = subSeedStrength, + denoisingStrength = denoisingStrength, + hidden = hidden, + liked = liked, + mediaPath = mediaPath, + inputMediaPath = inputMediaPath, + mediaType = MediaType.parse(mediaType), + modelName = modelName, + blurHash = blurHash, + ) +} +//endregion diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/FalAiEndpointMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/FalAiEndpointMappers.kt new file mode 100644 index 000000000..b3d76f299 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/FalAiEndpointMappers.kt @@ -0,0 +1,103 @@ +package dev.minios.pdaiv1.data.mappers + +import com.google.gson.Gson +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiEndpointCategory +import dev.minios.pdaiv1.domain.entity.FalAiEndpointSchema +import dev.minios.pdaiv1.domain.entity.FalAiInputProperty +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType +import dev.minios.pdaiv1.storage.db.persistent.entity.FalAiEndpointEntity + +private val gson = Gson() + +fun FalAiEndpointEntity.toDomain(): FalAiEndpoint = FalAiEndpoint( + id = id, + endpointId = endpointId, + title = title, + description = description, + category = FalAiEndpointCategory.fromKey(category), + group = group, + thumbnailUrl = thumbnailUrl, + playgroundUrl = playgroundUrl, + documentationUrl = documentationUrl, + isCustom = isCustom, + schema = gson.fromJson(schemaJson, FalAiEndpointSchemaDto::class.java).toDomain(), +) + +fun FalAiEndpoint.toEntity(): FalAiEndpointEntity = FalAiEndpointEntity( + id = id, + endpointId = endpointId, + title = title, + description = description, + category = category.key, + group = group, + thumbnailUrl = thumbnailUrl, + playgroundUrl = playgroundUrl, + documentationUrl = documentationUrl, + isCustom = isCustom, + schemaJson = gson.toJson(schema.toDto()), +) + +// DTOs for JSON serialization +private data class FalAiEndpointSchemaDto( + val baseUrl: String, + val submissionPath: String, + val inputProperties: List, + val requiredProperties: List, + val propertyOrder: List, +) + +private data class FalAiInputPropertyDto( + val name: String, + val title: String, + val description: String, + val type: String, + val default: Any?, + val minimum: Double?, + val maximum: Double?, + val enumValues: List?, + val isRequired: Boolean, + val isImageInput: Boolean, +) + +private fun FalAiEndpointSchemaDto.toDomain() = FalAiEndpointSchema( + baseUrl = baseUrl, + submissionPath = submissionPath, + inputProperties = inputProperties.map { it.toDomain() }, + requiredProperties = requiredProperties, + propertyOrder = propertyOrder, +) + +private fun FalAiInputPropertyDto.toDomain() = FalAiInputProperty( + name = name, + title = title, + description = description, + type = FalAiPropertyType.valueOf(type), + default = default, + minimum = minimum, + maximum = maximum, + enumValues = enumValues, + isRequired = isRequired, + isImageInput = isImageInput, +) + +private fun FalAiEndpointSchema.toDto() = FalAiEndpointSchemaDto( + baseUrl = baseUrl, + submissionPath = submissionPath, + inputProperties = inputProperties.map { it.toDto() }, + requiredProperties = requiredProperties, + propertyOrder = propertyOrder, +) + +private fun FalAiInputProperty.toDto() = FalAiInputPropertyDto( + name = name, + title = title, + description = description, + type = type.name, + default = default, + minimum = minimum?.toDouble(), + maximum = maximum?.toDouble(), + enumValues = enumValues, + isRequired = isRequired, + isImageInput = isImageInput, +) diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/FalAiOpenApiParser.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/FalAiOpenApiParser.kt new file mode 100644 index 000000000..4471a60a1 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/FalAiOpenApiParser.kt @@ -0,0 +1,337 @@ +package dev.minios.pdaiv1.data.mappers + +import com.google.gson.Gson +import com.google.gson.JsonObject +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiEndpointCategory +import dev.minios.pdaiv1.domain.entity.FalAiEndpointSchema +import dev.minios.pdaiv1.domain.entity.FalAiInputProperty +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType +import java.util.UUID + +/** + * Parses OpenAPI JSON schema into FalAiEndpoint domain entity. + */ +object FalAiOpenApiParser { + + private val gson = Gson() + + /** + * Parses an OpenAPI JSON schema into FalAiEndpoint. + * @param json The OpenAPI JSON content + * @param isCustom Whether this is a user-imported endpoint + * @param assetPath Optional asset path for built-in endpoints, used to extract group name + */ + fun parse(json: String, isCustom: Boolean = true, assetPath: String? = null): FalAiEndpoint { + val root = gson.fromJson(json, JsonObject::class.java) + + val info = root.getAsJsonObject("info") + val metadata = info.getAsJsonObject("x-fal-metadata") + + val endpointId = metadata.get("endpointId").asString + val id = if (isCustom) "custom-${UUID.randomUUID()}" else endpointId + + val category = FalAiEndpointCategory.fromKey( + metadata.get("category")?.asString ?: "other" + ) + + val schema = parseSchema(root) + + // Use endpointId as title, removing "fal-ai/" prefix + val title = endpointId.removePrefix("fal-ai/") + + // Extract group from asset path or use category as fallback + val group = extractGroupFromPath(assetPath) ?: category.displayName + + return FalAiEndpoint( + id = id, + endpointId = endpointId, + title = title, + description = info.get("description")?.asString ?: "", + category = category, + group = group, + thumbnailUrl = metadata.get("thumbnailUrl")?.asString ?: "", + playgroundUrl = metadata.get("playgroundUrl")?.asString ?: "", + documentationUrl = metadata.get("documentationUrl")?.asString ?: "", + isCustom = isCustom, + schema = schema, + ) + } + + private fun parseSchema(root: JsonObject): FalAiEndpointSchema { + val servers = root.getAsJsonArray("servers") + val baseUrl = servers?.get(0)?.asJsonObject?.get("url")?.asString + ?: "https://queue.fal.run" + + val paths = root.getAsJsonObject("paths") + val (submissionPath, inputSchemaRef) = findSubmissionEndpoint(paths) + + val components = root.getAsJsonObject("components") + val schemas = components?.getAsJsonObject("schemas") + + val inputSchema = resolveSchema(inputSchemaRef, schemas) + val (properties, required, order) = parseInputProperties(inputSchema, schemas) + + return FalAiEndpointSchema( + baseUrl = baseUrl, + submissionPath = submissionPath, + inputProperties = properties, + requiredProperties = required, + propertyOrder = order, + ) + } + + private fun findSubmissionEndpoint(paths: JsonObject): Pair { + for ((path, methods) in paths.entrySet()) { + val methodsObj = methods.asJsonObject + val post = methodsObj.getAsJsonObject("post") + if (post != null) { + val requestBody = post.getAsJsonObject("requestBody") + if (requestBody != null) { + val content = requestBody.getAsJsonObject("content") + val appJson = content?.getAsJsonObject("application/json") + val schema = appJson?.getAsJsonObject("schema") + return path to schema + } + } + } + throw IllegalArgumentException("No POST endpoint with requestBody found in OpenAPI schema") + } + + private fun resolveSchema(schemaRef: JsonObject?, schemas: JsonObject?): JsonObject? { + if (schemaRef == null) return null + + val ref = schemaRef.get("\$ref")?.asString + if (ref != null && schemas != null) { + val refName = ref.split("/").last() + return schemas.getAsJsonObject(refName) + } + return schemaRef + } + + private fun parseInputProperties( + inputSchema: JsonObject?, + schemas: JsonObject?, + ): Triple, List, List> { + if (inputSchema == null) return Triple(emptyList(), emptyList(), emptyList()) + + val properties = inputSchema.getAsJsonObject("properties") ?: return Triple(emptyList(), emptyList(), emptyList()) + val required = inputSchema.getAsJsonArray("required") + ?.map { it.asString } ?: emptyList() + val order = inputSchema.getAsJsonArray("x-fal-order-properties") + ?.map { it.asString } ?: properties.keySet().toList() + + // Check if this is an inpainting endpoint (has both image_url and mask_url in required) + val hasMaskUrl = properties.has("mask_url") + val hasImageUrl = properties.has("image_url") + val isInpaintingEndpoint = hasMaskUrl && hasImageUrl + + val parsedProperties = mutableListOf() + + for ((name, propElement) in properties.entrySet()) { + // Skip mask_url if this is an inpainting endpoint - it will be handled by image_url's INPAINT type + if (isInpaintingEndpoint && name == "mask_url") { + continue + } + + var prop = propElement.asJsonObject + val originalProp = prop // Keep original for default value extraction + + // Check if this is an image_size field with anyOf (ImageSize object + presets) + val anyOf = prop.getAsJsonArray("anyOf") + val isImageSizeField = name == "image_size" && anyOf != null && anyOf.any { element -> + val refStr = element.asJsonObject?.get("\$ref")?.asString + refStr?.contains("ImageSize") == true + } + + var imageSizePresets: List? = null + + if (isImageSizeField && anyOf != null) { + // Extract presets from enum option + val enumOption = anyOf.find { it.asJsonObject.has("enum") }?.asJsonObject + imageSizePresets = enumOption?.getAsJsonArray("enum")?.map { it.asString } + } else if (anyOf != null && anyOf.size() > 0) { + // Handle other anyOf - pick enum option or first option + val enumOption = anyOf.find { + it.asJsonObject.has("enum") + }?.asJsonObject + prop = enumOption ?: anyOf.get(0).asJsonObject + } + + // Resolve $ref if present (skip for image_size as we handle it specially) + if (!isImageSizeField) { + val ref = prop.get("\$ref")?.asString + if (ref != null && schemas != null) { + val refName = ref.split("/").last() + prop = schemas.getAsJsonObject(refName) ?: prop + } + } + + val isImageField = name.contains("image", ignoreCase = true) || + name.contains("mask", ignoreCase = true) + val hasEnum = prop.has("enum") + val typeStr = prop.get("type")?.asString + + // Check if this is an array of image URLs + val isImageArray = typeStr == "array" && isImageField && + originalProp.getAsJsonObject("items")?.get("type")?.asString == "string" + + val enumValues = when { + isImageSizeField -> imageSizePresets + hasEnum -> prop.getAsJsonArray("enum")?.map { it.asString } + else -> null + } + + // Determine if this is an inpainting image field (image_url with linked mask_url) + val isInpaintImageField = isInpaintingEndpoint && name == "image_url" + + val propertyType = when { + isInpaintImageField -> FalAiPropertyType.INPAINT + isImageSizeField -> FalAiPropertyType.IMAGE_SIZE + isImageArray -> FalAiPropertyType.IMAGE_URL_ARRAY + else -> FalAiPropertyType.fromString(typeStr, hasEnum, isImageField && typeStr == "string") + } + + // Parse array item properties if this is an array type + val arrayItemProperties = if (typeStr == "array") { + parseArrayItemProperties(originalProp, schemas) + } else null + + parsedProperties.add( + FalAiInputProperty( + name = name, + title = if (isInpaintImageField) "Image & Mask" else (prop.get("title")?.asString ?: name), + description = if (isInpaintImageField) { + "Select an image and draw a mask on the area to inpaint" + } else { + prop.get("description")?.asString ?: "" + }, + type = propertyType, + default = parseDefault(originalProp), + minimum = prop.get("minimum")?.asNumber, + maximum = prop.get("maximum")?.asNumber, + enumValues = enumValues, + isRequired = required.contains(name), + isImageInput = isImageField && (typeStr == "string" || prop.has("format")), + arrayItemProperties = arrayItemProperties, + linkedMaskProperty = if (isInpaintImageField) "mask_url" else null, + ) + ) + } + + return Triple(parsedProperties, required, order) + } + + private fun parseDefault(prop: JsonObject): Any? { + val default = prop.get("default") ?: return null + return when { + default.isJsonPrimitive -> { + val primitive = default.asJsonPrimitive + when { + primitive.isBoolean -> primitive.asBoolean + primitive.isNumber -> primitive.asNumber + primitive.isString -> primitive.asString + else -> primitive.asString + } + } + default.isJsonArray -> default.asJsonArray.map { it.asString } + else -> null + } + } + + /** + * Parses array item properties from the "items" field. + * Handles $ref resolution for object schemas like LoraWeight. + */ + private fun parseArrayItemProperties( + arrayProp: JsonObject, + schemas: JsonObject?, + ): List? { + val items = arrayProp.getAsJsonObject("items") ?: return null + + // Resolve $ref if present + var itemSchema = items + val ref = items.get("\$ref")?.asString + if (ref != null && schemas != null) { + val refName = ref.split("/").last() + itemSchema = schemas.getAsJsonObject(refName) ?: return null + } + + // Only parse if it's an object type with properties + if (itemSchema.get("type")?.asString != "object") return null + val properties = itemSchema.getAsJsonObject("properties") ?: return null + val required = itemSchema.getAsJsonArray("required")?.map { it.asString } ?: emptyList() + val order = itemSchema.getAsJsonArray("x-fal-order-properties")?.map { it.asString } + ?: properties.keySet().toList() + + val parsedProperties = mutableListOf() + + for (propName in order) { + val propElement = properties.get(propName) ?: continue + val prop = propElement.asJsonObject + + val hasEnum = prop.has("enum") + val typeStr = prop.get("type")?.asString + + val enumValues = if (hasEnum) { + prop.getAsJsonArray("enum")?.map { it.asString } + } else null + + parsedProperties.add( + FalAiInputProperty( + name = propName, + title = prop.get("title")?.asString ?: propName, + description = prop.get("description")?.asString ?: "", + type = FalAiPropertyType.fromString(typeStr, hasEnum, false), + default = parseDefault(prop), + minimum = prop.get("minimum")?.asNumber, + maximum = prop.get("maximum")?.asNumber, + enumValues = enumValues, + isRequired = required.contains(propName), + isImageInput = false, + ) + ) + } + + return parsedProperties.takeIf { it.isNotEmpty() } + } + + /** + * Extracts group name from asset path. + * E.g., "falai-endpoints/flux-2/edit/openapi.json" -> "FLUX 2" + */ + private fun extractGroupFromPath(assetPath: String?): String? { + if (assetPath == null) return null + + // Remove base directory prefix + val relativePath = assetPath + .removePrefix("falai-endpoints/") + .removePrefix("falai-endpoints\\") + + // Get the first directory segment + val firstSegment = relativePath.split("/", "\\").firstOrNull() + ?: return null + + // Format the segment into a readable group name + return formatGroupName(firstSegment) + } + + /** + * Formats a directory name like "flux-2" into a readable group name like "FLUX 2". + */ + private fun formatGroupName(dirName: String): String { + return dirName.split("-") + .filter { it.isNotBlank() } + .joinToString(" ") { word -> + when (word.lowercase()) { + "flux" -> "FLUX" + "lora" -> "LoRA" + "kontext" -> "Kontext" + "krea" -> "Krea" + "pro" -> "Pro" + "dev" -> "Dev" + else -> word.replaceFirstChar { it.uppercase() } + } + } + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/ForgeModulesMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ForgeModulesMappers.kt new file mode 100644 index 000000000..4970a2951 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ForgeModulesMappers.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.mappers + +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.network.model.ForgeModuleRaw + +fun List.mapRawToDomain(): List = map { it.mapRawToDomain() } + +fun ForgeModuleRaw.mapRawToDomain(): ForgeModule = ForgeModule( + name = modelName ?: "", + path = filename ?: "", +) diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/HuggingFaceModelMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/HuggingFaceModelMappers.kt similarity index 80% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/HuggingFaceModelMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/HuggingFaceModelMappers.kt index 3ed4ad390..f4054cf79 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/HuggingFaceModelMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/HuggingFaceModelMappers.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import com.shifthackz.aisdv1.network.model.HuggingFaceModelRaw -import com.shifthackz.aisdv1.storage.db.persistent.entity.HuggingFaceModelEntity +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.network.model.HuggingFaceModelRaw +import dev.minios.pdaiv1.storage.db.persistent.entity.HuggingFaceModelEntity //region RAW --> DOMAIN fun List.mapRawToCheckpointDomain(): List = diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/ImageToImagePayloadMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ImageToImagePayloadMappers.kt new file mode 100644 index 000000000..d897ab886 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ImageToImagePayloadMappers.kt @@ -0,0 +1,177 @@ +package dev.minios.pdaiv1.data.mappers + +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.Scheduler +import dev.minios.pdaiv1.domain.entity.StabilityAiClipGuidance +import dev.minios.pdaiv1.domain.entity.StabilityAiStylePreset +import dev.minios.pdaiv1.network.request.HordeGenerationAsyncRequest +import dev.minios.pdaiv1.network.request.HuggingFaceGenerationRequest +import dev.minios.pdaiv1.network.request.ImageToImageRequest +import dev.minios.pdaiv1.network.request.SwarmUiGenerationRequest +import dev.minios.pdaiv1.network.response.SdGenerationResponse +import java.util.Date + +//region PAYLOAD --> REQUEST +fun ImageToImagePayload.mapToRequest(): ImageToImageRequest = with(this) { + ImageToImageRequest( + initImages = listOf(base64Image), + includeInitImages = true, + mask = base64MaskImage.takeIf(String::isNotBlank), + inPaintingMaskInvert = inPaintingMaskInvert, + inPaintFullResPadding = inPaintFullResPadding, + inPaintingFill = inPaintingFill, + inPaintFullRes = inPaintFullRes, + maskBlur = maskBlur, + denoisingStrength = denoisingStrength, + prompt = prompt, + negativePrompt = negativePrompt, + steps = samplingSteps, + cfgScale = cfgScale, + width = width, + height = height, + restoreFaces = restoreFaces, + seed = seed.trim().ifEmpty { null }, + subSeed = subSeed.trim().ifEmpty { null }, + subSeedStrength = subSeedStrength, + samplerIndex = sampler, + scheduler = scheduler.takeIf { it != Scheduler.AUTOMATIC }?.alias, + alwaysOnScripts = aDetailer.toAlwaysOnScripts(), + ) +} + +fun ImageToImagePayload.mapToHordeRequest(): HordeGenerationAsyncRequest = with(this) { + HordeGenerationAsyncRequest( + prompt = prompt, + nsfw = nsfw, + sourceProcessing = "img2img", + sourceImage = base64Image, + params = HordeGenerationAsyncRequest.Params( + cfgScale = cfgScale, + width = width, + height = height, + steps = samplingSteps, + seed = seed.trim().ifEmpty { null }, + subSeedStrength = subSeedStrength.takeIf { it >= 0.1 }, + ) + ) +} + +fun ImageToImagePayload.mapToHuggingFaceRequest(): HuggingFaceGenerationRequest = with(this) { + HuggingFaceGenerationRequest( + inputs = base64Image, + parameters = buildMap { + this["width"] = width + this["height"] = height + prompt.trim().takeIf(String::isNotBlank)?.let { + this["text"] = it + } + negativePrompt.trim().takeIf(String::isNotBlank)?.let { + this["negative_prompt"] = it + } + seed.trim().takeIf(String::isNotBlank)?.let { + this["seed"] = it + } + this["num_inference_steps"] = samplingSteps + this["guidance_scale"] = cfgScale + } + ) +} + +fun ImageToImagePayload.mapToStabilityAiRequest() = with(this) { + buildMap { + buildList { + addAll(prompt.mapToStabilityPrompt(1.0)) + addAll(negativePrompt.mapToStabilityPrompt(-1.0)) + }.forEachIndexed { index, stpRaw -> + this["text_prompts[$index][text]"] = stpRaw.text + this["text_prompts[$index][weight]"] = stpRaw.weight.toString() + } + this["image_strength"] = "$denoisingStrength" + this["cfg_scale"] = "$cfgScale" + this["clip_guidance_preset"] = (stabilityAiClipGuidance ?: StabilityAiClipGuidance.NONE).toString() + this["seed"] = (seed.toLongOrNull()?.coerceIn(0L .. 4294967295L) ?: 0L).toString() + this["steps"] = "$samplingSteps" + stabilityAiStylePreset?.takeIf { it != StabilityAiStylePreset.NONE }?.key?.let { + this["style_preset"] = it + } + } +} + +fun ImageToImagePayload.mapToSwarmUiRequest( + sessionId: String, + swarmUiModel: String, +): SwarmUiGenerationRequest = with(this) { + SwarmUiGenerationRequest( + sessionId = sessionId, + model = swarmUiModel, + initImage = base64Image, + initImageCreativity = denoisingStrength.roundTo(2).toString(), + images = 1, + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + seed = seed.trim().ifEmpty { null }, + variationSeed = subSeed.trim().ifEmpty { null }, + variationSeedStrength = subSeedStrength.takeIf { it >= 0.1 }?.toString(), + cfgScale = cfgScale, + steps = samplingSteps, + ) +} +//endregion + +//region RESPONSE --> RESULT +fun Pair.mapToAiGenResult(): AiGenerationResult = + let { (payload, response) -> + AiGenerationResult( + id = 0L, + image = response.images?.firstOrNull() ?: "", + inputImage = payload.base64Image, + createdAt = Date(), + type = AiGenerationResult.Type.IMAGE_TO_IMAGE, + denoisingStrength = payload.denoisingStrength, + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + samplingSteps = payload.samplingSteps, + cfgScale = payload.cfgScale, + restoreFaces = payload.restoreFaces, + sampler = payload.sampler, + seed = if (payload.seed.trim().isNotEmpty()) payload.seed + else mapSeedFromRemote(response.info), + subSeed = if (payload.subSeed.trim().isNotEmpty()) payload.subSeed + else mapSubSeedFromRemote(response.info), + subSeedStrength = payload.subSeedStrength, + hidden = false, + modelName = payload.modelName, + ) + } + +fun Pair.mapCloudToAiGenResult(): AiGenerationResult = + let { (payload, base64) -> + AiGenerationResult( + id = 0L, + image = base64, + inputImage = payload.base64Image, + createdAt = Date(), + type = AiGenerationResult.Type.IMAGE_TO_IMAGE, + denoisingStrength = payload.denoisingStrength, + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + samplingSteps = payload.samplingSteps, + cfgScale = payload.cfgScale, + restoreFaces = payload.restoreFaces, + sampler = payload.sampler, + seed = payload.seed, + subSeed = payload.subSeed, + subSeedStrength = payload.subSeedStrength, + hidden = false, + modelName = payload.modelName, + ) + } +//endregion diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/LocalAiModelMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/LocalAiModelMappers.kt new file mode 100644 index 000000000..e72d5d5ad --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/LocalAiModelMappers.kt @@ -0,0 +1,51 @@ +package dev.minios.pdaiv1.data.mappers + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.network.response.DownloadableModelResponse +import dev.minios.pdaiv1.storage.db.persistent.entity.LocalModelEntity + +//region RAW --> DOMAIN +fun List.mapRawToCheckpointDomain( + type: LocalAiModel.Type, +): List = map { it.mapRawToCheckpointDomain(type) } + +fun DownloadableModelResponse.mapRawToCheckpointDomain( + type: LocalAiModel.Type, +): LocalAiModel = with(this) { + LocalAiModel( + id = id ?: "", + type = type, + name = name ?: "", + size = size ?: "", + sources = sources ?: emptyList(), + chipsetSuffix = metadata?.chipset, + runOnCpu = metadata?.type == "cpu", + ) +} +//endregion + +//region DOMAIN --> ENTITY +fun List.mapDomainToEntity(): List = + map(LocalAiModel::mapDomainToEntity) + +fun LocalAiModel.mapDomainToEntity(): LocalModelEntity = with(this) { + LocalModelEntity(id, type.key, name, size, sources, chipsetSuffix, runOnCpu) +} +//endregion + +//region ENTITY --> DOMAIN +fun List.mapEntityToDomain(): List = + map(LocalModelEntity::mapEntityToDomain) + +fun LocalModelEntity.mapEntityToDomain(): LocalAiModel = with(this) { + LocalAiModel( + id = id, + type = LocalAiModel.Type.parse(type), + name = name, + size = size, + sources = sources, + chipsetSuffix = chipsetSuffix, + runOnCpu = runOnCpu, + ) +} +//endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ResponseParamsMapper.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ResponseParamsMapper.kt similarity index 79% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/ResponseParamsMapper.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/ResponseParamsMapper.kt index 415e8d5a3..5df8c0392 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ResponseParamsMapper.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ResponseParamsMapper.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.network.response.SdGenerationResponse +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.network.response.SdGenerationResponse fun mapSeedFromRemote(infoString: String?): String = parseInfo(infoString).fold( onFailure = { "" }, diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ServerConfigurationMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ServerConfigurationMappers.kt similarity index 77% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/ServerConfigurationMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/ServerConfigurationMappers.kt index 5686fcfaf..ebfffa1c8 100755 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ServerConfigurationMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/ServerConfigurationMappers.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration -import com.shifthackz.aisdv1.network.model.ServerConfigurationRaw -import com.shifthackz.aisdv1.storage.db.cache.entity.ServerConfigurationEntity +import dev.minios.pdaiv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.network.model.ServerConfigurationRaw +import dev.minios.pdaiv1.storage.db.cache.entity.ServerConfigurationEntity //region RAW --> DOMAIN fun ServerConfigurationRaw.mapToDomain(): ServerConfiguration = with(this) { diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/StabilityAiEngineMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StabilityAiEngineMappers.kt new file mode 100644 index 000000000..3756ffd4e --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StabilityAiEngineMappers.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.data.mappers + +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine +import dev.minios.pdaiv1.network.model.StabilityAiEngineRaw + +//region RAW --> DOMAIN +fun List.mapRawToCheckpointDomain(): List = + map(StabilityAiEngineRaw::mapRawToCheckpointDomain) + +fun StabilityAiEngineRaw.mapRawToCheckpointDomain(): StabilityAiEngine = with(this) { + StabilityAiEngine(id ?: "", name ?: "") +} +//endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StabilityAiPromptMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StabilityAiPromptMappers.kt similarity index 88% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/StabilityAiPromptMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/StabilityAiPromptMappers.kt index 27d21f866..15f59df37 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StabilityAiPromptMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StabilityAiPromptMappers.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.network.model.StabilityTextPromptRaw +import dev.minios.pdaiv1.network.model.StabilityTextPromptRaw fun String.mapToStabilityPrompt(defaultWeight: Double = 1.0): List = buildList { diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionEmbeddingsMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionEmbeddingsMappers.kt similarity index 75% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionEmbeddingsMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionEmbeddingsMappers.kt index 8403d4364..d190f9b07 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionEmbeddingsMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionEmbeddingsMappers.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.network.response.SdEmbeddingsResponse -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.network.response.SdEmbeddingsResponse +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity //region RAW -> DOMAIN fun SdEmbeddingsResponse.mapRawToCheckpointDomain(): List = diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionHyperNetworksMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionHyperNetworksMappers.kt similarity index 80% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionHyperNetworksMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionHyperNetworksMappers.kt index 0690d860a..ee097c4e6 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionHyperNetworksMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionHyperNetworksMappers.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork -import com.shifthackz.aisdv1.network.model.StableDiffusionHyperNetworkRaw -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork +import dev.minios.pdaiv1.network.model.StableDiffusionHyperNetworkRaw +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity //region RAW -> DOMAIN fun List.mapRawToCheckpointDomain(): List = diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionLorasMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionLorasMappers.kt similarity index 80% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionLorasMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionLorasMappers.kt index ab3829982..a5d1eba15 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionLorasMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionLorasMappers.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.network.model.StableDiffusionLoraRaw -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionLoraEntity +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.network.model.StableDiffusionLoraRaw +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionLoraEntity //region RAW --> DOMAIN fun List.mapToDomain(): List = diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionModelsMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionModelsMappers.kt similarity index 85% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionModelsMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionModelsMappers.kt index b9d03c2d4..cd3fb8796 100755 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionModelsMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionModelsMappers.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel -import com.shifthackz.aisdv1.network.model.StableDiffusionModelRaw -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionModelEntity +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel +import dev.minios.pdaiv1.network.model.StableDiffusionModelRaw +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionModelEntity //region RAW --> DOMAIN fun List.mapRawToCheckpointDomain(): List = diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionSamplersMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionSamplersMappers.kt similarity index 82% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionSamplersMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionSamplersMappers.kt index dd932fa89..1ce7c7a1e 100755 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/StableDiffusionSamplersMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/StableDiffusionSamplersMappers.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import com.shifthackz.aisdv1.network.model.StableDiffusionSamplerRaw -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionSamplerEntity +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.network.model.StableDiffusionSamplerRaw +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionSamplerEntity //region RAW --> DOMAIN fun List.mapRawToCheckpointDomain(): List = diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/SupportersMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/SupportersMappers.kt similarity index 77% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/SupportersMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/SupportersMappers.kt index f3559e51e..0004b607d 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/SupportersMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/SupportersMappers.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.core.common.extensions.toDate -import com.shifthackz.aisdv1.domain.entity.Supporter -import com.shifthackz.aisdv1.network.model.SupporterRaw -import com.shifthackz.aisdv1.storage.db.persistent.entity.SupporterEntity +import dev.minios.pdaiv1.core.common.extensions.toDate +import dev.minios.pdaiv1.domain.entity.Supporter +import dev.minios.pdaiv1.network.model.SupporterRaw +import dev.minios.pdaiv1.storage.db.persistent.entity.SupporterEntity import java.util.Date //region RAW --> DOMAIN diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/SwarmUiModelsMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/SwarmUiModelsMappers.kt similarity index 83% rename from data/src/main/java/com/shifthackz/aisdv1/data/mappers/SwarmUiModelsMappers.kt rename to data/src/main/java/dev/minios/pdaiv1/data/mappers/SwarmUiModelsMappers.kt index 931fa4d07..42e0382e3 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/SwarmUiModelsMappers.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/SwarmUiModelsMappers.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.data.mappers +package dev.minios.pdaiv1.data.mappers -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import com.shifthackz.aisdv1.network.model.SwarmUiModelRaw -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse -import com.shifthackz.aisdv1.storage.db.cache.entity.SwarmUiModelEntity +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import dev.minios.pdaiv1.network.model.SwarmUiModelRaw +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse +import dev.minios.pdaiv1.storage.db.cache.entity.SwarmUiModelEntity //region RAW --> CHECKPOINT DOMAIN fun SwarmUiModelsResponse.mapRawToCheckpointDomain(): List = with(this) { diff --git a/data/src/main/java/dev/minios/pdaiv1/data/mappers/TextToImagePayloadMappers.kt b/data/src/main/java/dev/minios/pdaiv1/data/mappers/TextToImagePayloadMappers.kt new file mode 100755 index 000000000..3112dfc3a --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/mappers/TextToImagePayloadMappers.kt @@ -0,0 +1,301 @@ +package dev.minios.pdaiv1.data.mappers + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.Scheduler +import dev.minios.pdaiv1.domain.entity.StabilityAiClipGuidance +import dev.minios.pdaiv1.domain.entity.StabilityAiSampler +import dev.minios.pdaiv1.domain.entity.StabilityAiStylePreset +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.network.request.HordeGenerationAsyncRequest +import dev.minios.pdaiv1.network.request.HuggingFaceGenerationRequest +import dev.minios.pdaiv1.network.request.OpenAiRequest +import dev.minios.pdaiv1.network.request.OverrideSettings +import dev.minios.pdaiv1.network.request.StabilityTextToImageRequest +import dev.minios.pdaiv1.network.request.SwarmUiGenerationRequest +import dev.minios.pdaiv1.network.request.TextToImageRequest +import dev.minios.pdaiv1.network.response.SdGenerationResponse +import java.util.Date + +//region PAYLOAD --> REQUEST +fun TextToImagePayload.mapToRequest(): TextToImageRequest = with(this) { + TextToImageRequest( + prompt = prompt, + negativePrompt = negativePrompt, + steps = samplingSteps, + cfgScale = cfgScale, + distilledCfgScale = distilledCfgScale, + width = width, + height = height, + restoreFaces = restoreFaces, + seed = seed.trim().ifEmpty { null }, + subSeed = subSeed.trim().ifEmpty { null }, + subSeedStrength = subSeedStrength, + samplerIndex = sampler, + scheduler = scheduler.takeIf { it != Scheduler.AUTOMATIC }?.alias, + alwaysOnScripts = aDetailer.toAlwaysOnScripts(), + enableHr = hires.enabled.takeIf { it }, + hrUpscaler = hires.upscaler.takeIf { hires.enabled }, + hrScale = hires.scale.takeIf { hires.enabled }, + hrSecondPassSteps = hires.steps.takeIf { hires.enabled && it > 0 }, + hrCfg = hires.hrCfg?.takeIf { hires.enabled }, + hrDistilledCfg = hires.hrDistilledCfg?.takeIf { hires.enabled }, + hrAdditionalModules = if (hires.enabled) emptyList() else null, + denoisingStrength = hires.denoisingStrength.takeIf { hires.enabled }, + overrideSettings = forgeModules.takeIf { it.isNotEmpty() }?.let { modules -> + OverrideSettings(forgeAdditionalModules = modules.map { it.path }) + }, + ) +} + +fun TextToImagePayload.mapToHordeRequest(): HordeGenerationAsyncRequest = with(this) { + HordeGenerationAsyncRequest( + prompt = prompt, + nsfw = nsfw, + sourceProcessing = null, + sourceImage = null, + params = HordeGenerationAsyncRequest.Params( + cfgScale = cfgScale, + width = width, + height = height, + steps = samplingSteps, + seed = seed.trim().ifEmpty { null }, + subSeedStrength = subSeedStrength.takeIf { it >= 0.1 }, + ) + ) +} + +fun TextToImagePayload.mapToHuggingFaceRequest(): HuggingFaceGenerationRequest = with(this) { + HuggingFaceGenerationRequest( + inputs = prompt, + parameters = buildMap { + this["width"] = width + this["height"] = height + negativePrompt.trim().takeIf(String::isNotBlank)?.let { + this["negative_prompt"] = it + } + seed.trim().takeIf(String::isNotBlank)?.let { + this["seed"] = it + } + this["num_inference_steps"] = samplingSteps + this["guidance_scale"] = cfgScale + }, + ) +} + +fun TextToImagePayload.mapToOpenAiRequest(): OpenAiRequest = with(this) { + OpenAiRequest( + prompt = prompt, + model = openAiModel?.alias ?: OpenAiModel.DALL_E_2.alias, + size = "${width}x${height}", + responseFormat = "b64_json", + quality = quality, + style = style, + ) +} + +fun TextToImagePayload.mapToStabilityAiRequest(): StabilityTextToImageRequest = with(this) { + StabilityTextToImageRequest( + height = height, + width = width, + textPrompts = buildList { + addAll(prompt.mapToStabilityPrompt(1.0)) + addAll(negativePrompt.mapToStabilityPrompt(-1.0)) + }, + cfgScale = cfgScale, + clipGuidancePreset = (stabilityAiClipGuidance ?: StabilityAiClipGuidance.NONE).toString(), + sampler = sampler + .takeIf { it != "${StabilityAiSampler.NONE}" } + .takeIf { StabilityAiSampler.entries.map { s -> "$s" }.contains(it) }, + seed = seed.toLongOrNull()?.coerceIn(0L .. 4294967295L) ?: 0L, + steps = samplingSteps, + stylePreset = stabilityAiStylePreset?.takeIf { it != StabilityAiStylePreset.NONE }?.key, + ) +} + +fun TextToImagePayload.mapToSwarmUiRequest( + sessionId: String, + swarmUiModel: String, +): SwarmUiGenerationRequest = with(this) { + SwarmUiGenerationRequest( + sessionId = sessionId, + model = swarmUiModel, + initImage = null, + initImageCreativity = null, + images = 1, + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + seed = seed.trim().ifEmpty { null }, + variationSeed = subSeed.trim().ifEmpty { null }, + variationSeedStrength = subSeedStrength.takeIf { it >= 0.1 }?.toString(), + cfgScale = cfgScale, + steps = samplingSteps, + ) +} +//endregion + +//region RESPONSE --> RESULT +fun Pair.mapToAiGenResult(): AiGenerationResult = + let { (payload, response) -> + AiGenerationResult( + id = 0L, + image = response.images?.firstOrNull() ?: "", + inputImage = "", + createdAt = Date(), + type = AiGenerationResult.Type.TEXT_TO_IMAGE, + denoisingStrength = 0f, + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + samplingSteps = payload.samplingSteps, + cfgScale = payload.cfgScale, + restoreFaces = payload.restoreFaces, + sampler = payload.sampler, + seed = if (payload.seed.trim().isNotEmpty()) payload.seed + else mapSeedFromRemote(response.info), + subSeed = if (payload.subSeed.trim().isNotEmpty()) payload.subSeed + else mapSubSeedFromRemote(response.info), + subSeedStrength = payload.subSeedStrength, + hidden = false, + modelName = payload.modelName, + ) + } + +fun Pair.mapCloudToAiGenResult(): AiGenerationResult = + let { (payload, base64) -> + AiGenerationResult( + id = 0L, + image = base64, + inputImage = "", + createdAt = Date(), + type = AiGenerationResult.Type.TEXT_TO_IMAGE, + denoisingStrength = 0f, + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + samplingSteps = payload.samplingSteps, + cfgScale = payload.cfgScale, + restoreFaces = payload.restoreFaces, + sampler = payload.sampler, + seed = payload.seed, + subSeed = payload.subSeed, + subSeedStrength = payload.subSeedStrength, + hidden = false, + modelName = payload.modelName, + ) + } + +fun Pair.mapLocalDiffusionToAiGenResult(): AiGenerationResult = + let { (payload, base64) -> + AiGenerationResult( + id = 0L, + image = base64, + inputImage = "", + createdAt = Date(), + type = AiGenerationResult.Type.TEXT_TO_IMAGE, + denoisingStrength = 0f, + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + samplingSteps = payload.samplingSteps, + cfgScale = payload.cfgScale, + restoreFaces = payload.restoreFaces, + sampler = payload.sampler, + seed = payload.seed, + subSeed = payload.subSeed, + subSeedStrength = payload.subSeedStrength, + hidden = false, + modelName = payload.modelName, + ) + } +//endregion + +//region QNN Mappers +sealed interface QnnGenerationPayload { + val seed: Long +} + +data class QnnGenerationData private constructor( + private val textPayload: TextToImagePayload?, + private val imagePayload: ImageToImagePayload?, + val base64: String, + override val seed: Long, + val width: Int, + val height: Int, +) : QnnGenerationPayload { + + constructor( + payload: TextToImagePayload, + base64: String, + seed: Long, + width: Int, + height: Int, + ) : this(payload, null, base64, seed, width, height) + + constructor( + payload: ImageToImagePayload, + base64: String, + seed: Long, + width: Int, + height: Int, + ) : this(null, payload, base64, seed, width, height) + + val isTextToImage: Boolean get() = textPayload != null + + val txt2ImgPayload: TextToImagePayload get() = textPayload!! + + val img2ImgPayload: ImageToImagePayload get() = imagePayload!! +} + +fun QnnGenerationData.mapQnnResultToAiGenResult(): AiGenerationResult = if (isTextToImage) { + AiGenerationResult( + id = 0L, + image = base64, + inputImage = "", + createdAt = Date(), + type = AiGenerationResult.Type.TEXT_TO_IMAGE, + denoisingStrength = 0f, + prompt = txt2ImgPayload.prompt, + negativePrompt = txt2ImgPayload.negativePrompt, + width = width, + height = height, + samplingSteps = txt2ImgPayload.samplingSteps, + cfgScale = txt2ImgPayload.cfgScale, + restoreFaces = txt2ImgPayload.restoreFaces, + sampler = txt2ImgPayload.sampler, + seed = seed.toString(), + subSeed = txt2ImgPayload.subSeed, + subSeedStrength = txt2ImgPayload.subSeedStrength, + hidden = false, + modelName = txt2ImgPayload.modelName, + ) +} else { + AiGenerationResult( + id = 0L, + image = base64, + inputImage = img2ImgPayload.base64Image, + createdAt = Date(), + type = AiGenerationResult.Type.IMAGE_TO_IMAGE, + denoisingStrength = img2ImgPayload.denoisingStrength, + prompt = img2ImgPayload.prompt, + negativePrompt = img2ImgPayload.negativePrompt, + width = width, + height = height, + samplingSteps = img2ImgPayload.samplingSteps, + cfgScale = img2ImgPayload.cfgScale, + restoreFaces = img2ImgPayload.restoreFaces, + sampler = img2ImgPayload.sampler, + seed = seed.toString(), + subSeed = img2ImgPayload.subSeed, + subSeedStrength = img2ImgPayload.subSeedStrength, + hidden = false, + modelName = img2ImgPayload.modelName, + ) +} +//endregion diff --git a/data/src/main/java/dev/minios/pdaiv1/data/preference/PreferenceManagerImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/preference/PreferenceManagerImpl.kt new file mode 100644 index 000000000..7ec3a4d20 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/preference/PreferenceManagerImpl.kt @@ -0,0 +1,421 @@ +package dev.minios.pdaiv1.data.preference + +import android.content.SharedPreferences +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import dev.minios.pdaiv1.core.common.extensions.fixUrlSlashes +import dev.minios.pdaiv1.core.common.extensions.shouldUseNewMediaStore +import dev.minios.pdaiv1.core.common.file.LOCAL_DIFFUSION_CUSTOM_PATH +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.entity.FeatureTag +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import com.shifthackz.android.core.preferences.delegates +import io.reactivex.rxjava3.core.BackpressureStrategy +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.subjects.BehaviorSubject + +class PreferenceManagerImpl( + private val preferences: SharedPreferences, +) : PreferenceManager { + + private val gson = Gson() + + private val preferencesChangedSubject: BehaviorSubject = + BehaviorSubject.createDefault(Unit) + + override var automatic1111ServerUrl: String by preferences.delegates.complexString( + key = KEY_SERVER_URL, + default = "", + serialize = { it.fixUrlSlashes() }, + deserialize = { it.fixUrlSlashes() }, + onChanged = ::onPreferencesChanged, + ) + + override var swarmUiServerUrl: String by preferences.delegates.complexString( + key = KEY_SWARM_SERVER_URL, + default = "", + serialize = { it.fixUrlSlashes() }, + deserialize = { it.fixUrlSlashes() }, + onChanged = ::onPreferencesChanged, + ) + + override var swarmUiModel: String by preferences.delegates.string( + key = KEY_SWARM_MODEL, + onChanged = ::onPreferencesChanged, + ) + + override var demoMode: Boolean by preferences.delegates.boolean( + key = KEY_DEMO_MODE, + onChanged = ::onPreferencesChanged, + ) + + override var developerMode: Boolean by preferences.delegates.boolean( + key = KEY_DEVELOPER_MODE, + onChanged = ::onPreferencesChanged, + ) + + override var localMediaPipeCustomModelPath: String by preferences.delegates.string( + key = KEY_MEDIA_PIPE_CUSTOM_MODEL_PATH, + default = LOCAL_DIFFUSION_CUSTOM_PATH, + ) + + override var localQnnCustomModelPath: String by preferences.delegates.string( + key = KEY_QNN_CUSTOM_MODEL_PATH, + default = LOCAL_DIFFUSION_CUSTOM_PATH, + ) + + override var localOnnxCustomModelPath: String by preferences.delegates.string( + key = KEY_LOCAL_DIFFUSION_CUSTOM_MODEL_PATH, + default = LOCAL_DIFFUSION_CUSTOM_PATH, + ) + + override var localOnnxAllowCancel: Boolean by preferences.delegates.boolean( + key = KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, + onChanged = ::onPreferencesChanged, + ) + + override var localOnnxSchedulerThread: SchedulersToken by preferences.delegates.complexInt( + key = KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD, + default = SchedulersToken.COMPUTATION, + serialize = { token -> token.ordinal }, + deserialize = { index -> SchedulersToken.entries[index] }, + onChanged = ::onPreferencesChanged, + ) + + override var monitorConnectivity: Boolean by preferences.delegates.complexBoolean( + key = KEY_MONITOR_CONNECTIVITY, + default = false, + serialize = { it }, + deserialize = { if (!source.featureTags.contains(FeatureTag.OwnServer)) false else it }, + onChanged = ::onPreferencesChanged, + ) + + override var autoSaveAiResults: Boolean by preferences.delegates.boolean( + key = KEY_AI_AUTO_SAVE, + default = true, + onChanged = ::onPreferencesChanged, + ) + + override var saveToMediaStore: Boolean by preferences.delegates.boolean( + key = KEY_SAVE_TO_MEDIA_STORE, + default = shouldUseNewMediaStore(), + onChanged = ::onPreferencesChanged, + ) + + override var formAdvancedOptionsAlwaysShow: Boolean by preferences.delegates.boolean( + key = KEY_FORM_ALWAYS_SHOW_ADVANCED_OPTIONS, + onChanged = ::onPreferencesChanged, + ) + + override var formPromptTaggedInput: Boolean by preferences.delegates.boolean( + key = KEY_FORM_PROMPT_TAGGED_INPUT, + default = false, + onChanged = ::onPreferencesChanged, + ) + + override var source: ServerSource by preferences.delegates.complexString( + key = KEY_SERVER_SOURCE, + default = ServerSource.AUTOMATIC1111, + serialize = { source -> source.key }, + deserialize = { key -> ServerSource.parse(key) }, + onChanged = ::onPreferencesChanged, + ) + + override var sdModel: String by preferences.delegates.string( + key = KEY_SD_MODEL, + onChanged = ::onPreferencesChanged, + ) + + override var modelType: ModelType by preferences.delegates.complexString( + key = KEY_MODEL_TYPE, + default = ModelType.SD_1_5, + serialize = { type -> type.name }, + deserialize = { name -> ModelType.entries.find { it.name == name } ?: ModelType.SD_1_5 }, + onChanged = ::onPreferencesChanged, + ) + + override var hordeApiKey: String by preferences.delegates.string( + key = KEY_HORDE_API_KEY, + onChanged = ::onPreferencesChanged, + ) + + override var openAiApiKey: String by preferences.delegates.string( + key = KEY_OPEN_AI_API_KEY, + onChanged = ::onPreferencesChanged, + ) + + override var huggingFaceApiKey: String by preferences.delegates.string( + key = KEY_HUGGING_FACE_API_KEY, + onChanged = ::onPreferencesChanged, + ) + + override var huggingFaceModel: String by preferences.delegates.string( + key = KEY_HUGGING_FACE_MODEL_KEY, + default = HuggingFaceModel.default.alias, + onChanged = ::onPreferencesChanged, + ) + + override var stabilityAiApiKey: String by preferences.delegates.string( + key = KEY_STABILITY_AI_API_KEY, + onChanged = ::onPreferencesChanged, + ) + + override var stabilityAiEngineId: String by preferences.delegates.string( + key = KEY_STABILITY_AI_ENGINE_ID_KEY, + onChanged = ::onPreferencesChanged, + ) + + override var falAiApiKey: String by preferences.delegates.string( + key = KEY_FAL_AI_API_KEY, + onChanged = ::onPreferencesChanged, + ) + + override var falAiSelectedEndpointId: String by preferences.delegates.string( + key = KEY_FAL_AI_SELECTED_ENDPOINT_ID, + onChanged = ::onPreferencesChanged, + ) + + override var onBoardingComplete: Boolean by preferences.delegates.boolean( + key = KEY_ON_BOARDING_COMPLETE, + ) + + override var forceSetupAfterUpdate: Boolean by preferences.delegates.boolean( + key = KEY_FORCE_SETUP_AFTER_UPDATE, + default = true, + onChanged = ::onPreferencesChanged, + ) + + override var localOnnxModelId: String by preferences.delegates.string( + key = KEY_LOCAL_MODEL_ID, + onChanged = ::onPreferencesChanged, + ) + + override var localOnnxUseNNAPI: Boolean by preferences.delegates.boolean( + key = KEY_LOCAL_NN_API, + onChanged = ::onPreferencesChanged, + ) + + override var localMediaPipeModelId: String by preferences.delegates.string( + key = KEY_MEDIA_PIPE_MODEL_ID, + onChanged = ::onPreferencesChanged, + ) + + override var localQnnModelId: String by preferences.delegates.string( + key = KEY_QNN_MODEL_ID, + onChanged = ::onPreferencesChanged, + ) + + override var localQnnRunOnCpu: Boolean by preferences.delegates.boolean( + key = KEY_QNN_RUN_ON_CPU, + default = false, + onChanged = ::onPreferencesChanged, + ) + + override var localQnnUseOpenCL: Boolean by preferences.delegates.boolean( + key = KEY_QNN_USE_OPENCL, + default = false, + onChanged = ::onPreferencesChanged, + ) + + override var localQnnScheduler: String by preferences.delegates.string( + key = KEY_QNN_SCHEDULER, + default = "dpm", + onChanged = ::onPreferencesChanged, + ) + + override var localQnnShowDiffusionProcess: Boolean by preferences.delegates.boolean( + key = KEY_QNN_SHOW_DIFFUSION_PROCESS, + default = false, + onChanged = ::onPreferencesChanged, + ) + + override var localQnnLastPrompt: String by preferences.delegates.string( + key = KEY_QNN_LAST_PROMPT, + default = "", + ) + + override var localQnnLastNegativePrompt: String by preferences.delegates.string( + key = KEY_QNN_LAST_NEGATIVE_PROMPT, + default = "", + ) + + override var localOnnxLastPrompt: String by preferences.delegates.string( + key = KEY_ONNX_LAST_PROMPT, + default = "", + ) + + override var localOnnxLastNegativePrompt: String by preferences.delegates.string( + key = KEY_ONNX_LAST_NEGATIVE_PROMPT, + default = "", + ) + + override var localMediaPipeLastPrompt: String by preferences.delegates.string( + key = KEY_MEDIAPIPE_LAST_PROMPT, + default = "", + ) + + override var localMediaPipeLastNegativePrompt: String by preferences.delegates.string( + key = KEY_MEDIAPIPE_LAST_NEGATIVE_PROMPT, + default = "", + ) + + override var designUseSystemColorPalette: Boolean by preferences.delegates.boolean( + key = KEY_DESIGN_DYNAMIC_COLORS, + onChanged = ::onPreferencesChanged, + ) + + override var designUseSystemDarkTheme: Boolean by preferences.delegates.boolean( + key = KEY_DESIGN_SYSTEM_DARK_THEME, + default = true, + onChanged = ::onPreferencesChanged, + ) + + override var designDarkTheme: Boolean by preferences.delegates.boolean( + key = KEY_DESIGN_DARK_THEME, + default = true, + onChanged = ::onPreferencesChanged, + ) + + override var designColorToken: String by preferences.delegates.string( + key = KEY_DESIGN_COLOR_TOKEN, + default = "${ColorToken.MAUVE}", + onChanged = ::onPreferencesChanged, + ) + + override var designDarkThemeToken: String by preferences.delegates.string( + key = KEY_DESIGN_DARK_TOKEN, + default = "${DarkThemeToken.FRAPPE}", + onChanged = ::onPreferencesChanged, + ) + override var backgroundGeneration: Boolean by preferences.delegates.boolean( + key = KEY_BACKGROUND_GENERATION, + onChanged = ::onPreferencesChanged, + ) + + override var backgroundProcessCount: Int by preferences.delegates.int( + key = KEY_BACKGROUND_PROCESS_COUNT, + default = 0, + ) + + override var galleryGrid: Grid by preferences.delegates.complexInt( + key = KEY_GALLERY_GRID, + default = Grid.Fixed2, + serialize = { grid -> grid.size }, + deserialize = { size -> Grid.fromSize(size) }, + onChanged = ::onPreferencesChanged, + ) + + override fun observe(): Flowable = preferencesChangedSubject + .toFlowable(BackpressureStrategy.LATEST) + .map { + Settings( + serverUrl = automatic1111ServerUrl, + sdModel = sdModel, + modelType = modelType, + demoMode = demoMode, + developerMode = developerMode, + localDiffusionAllowCancel = localOnnxAllowCancel, + localDiffusionSchedulerThread = localOnnxSchedulerThread, + monitorConnectivity = monitorConnectivity, + backgroundGeneration = backgroundGeneration, + autoSaveAiResults = autoSaveAiResults, + saveToMediaStore = saveToMediaStore, + formAdvancedOptionsAlwaysShow = formAdvancedOptionsAlwaysShow, + formPromptTaggedInput = formPromptTaggedInput, + source = source, + hordeApiKey = hordeApiKey, + localUseNNAPI = localOnnxUseNNAPI, + designUseSystemColorPalette = designUseSystemColorPalette, + designUseSystemDarkTheme = designUseSystemDarkTheme, + designDarkTheme = designDarkTheme, + designColorToken = designColorToken, + designDarkThemeToken = designDarkThemeToken, + galleryGrid = galleryGrid, + ) + } + + override fun refresh(): Completable = Completable.fromAction { + preferencesChangedSubject.onNext(Unit) + } + + private fun onPreferencesChanged(value: T) = preferencesChangedSubject.onNext(value) + + override fun saveFalAiEndpointParams(endpointId: String, params: Map) { + val key = "${KEY_FAL_AI_ENDPOINT_PARAMS_PREFIX}$endpointId" + val json = gson.toJson(params) + preferences.edit().putString(key, json).apply() + } + + override fun getFalAiEndpointParams(endpointId: String): Map { + val key = "${KEY_FAL_AI_ENDPOINT_PARAMS_PREFIX}$endpointId" + val json = preferences.getString(key, null) ?: return emptyMap() + return try { + val type = object : TypeToken>() {}.type + gson.fromJson(json, type) ?: emptyMap() + } catch (e: Exception) { + emptyMap() + } + } + + companion object { + const val KEY_SERVER_URL = "key_server_url" + const val KEY_SWARM_SERVER_URL = "key_swarm_server_url" + const val KEY_SWARM_MODEL = "key_swarm_model" + const val KEY_DEMO_MODE = "key_demo_mode" + const val KEY_DEVELOPER_MODE = "key_developer_mode" + const val KEY_LOCAL_DIFFUSION_CUSTOM_MODEL_PATH = "key_local_diffusion_custom_model_path" + const val KEY_MEDIA_PIPE_CUSTOM_MODEL_PATH = "key_mediapipe_custom_model_path" + const val KEY_QNN_CUSTOM_MODEL_PATH = "key_qnn_custom_model_path" + const val KEY_ALLOW_LOCAL_DIFFUSION_CANCEL = "key_allow_local_diffusion_cancel" + const val KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD = "key_local_diffusion_scheduler_thread" + const val KEY_MONITOR_CONNECTIVITY = "key_monitor_connection" + const val KEY_AI_AUTO_SAVE = "key_ai_auto_save" + const val KEY_SAVE_TO_MEDIA_STORE = "key_save_to_media_store" + const val KEY_FORM_ALWAYS_SHOW_ADVANCED_OPTIONS = "key_always_show_advanced_options" + const val KEY_FORM_PROMPT_TAGGED_INPUT = "key_prompt_tagged_input_kb" + const val KEY_SERVER_SOURCE = "key_server_source" + const val KEY_SD_MODEL = "key_sd_model" + const val KEY_MODEL_TYPE = "key_model_type" + const val KEY_HORDE_API_KEY = "key_horde_api_key" + const val KEY_OPEN_AI_API_KEY = "key_open_ai_api_key" + const val KEY_HUGGING_FACE_API_KEY = "key_hugging_face_api_key" + const val KEY_HUGGING_FACE_MODEL_KEY = "key_hugging_face_model_key" + const val KEY_STABILITY_AI_API_KEY = "key_stability_ai_api_key" + const val KEY_STABILITY_AI_ENGINE_ID_KEY = "key_stability_ai_engine_id_key" + const val KEY_FAL_AI_API_KEY = "key_fal_ai_api_key" + const val KEY_FAL_AI_SELECTED_ENDPOINT_ID = "key_fal_ai_selected_endpoint_id" + const val KEY_FAL_AI_ENDPOINT_PARAMS_PREFIX = "key_fal_ai_endpoint_params_" + const val KEY_ON_BOARDING_COMPLETE = "key_on_boarding_complete" + const val KEY_FORCE_SETUP_AFTER_UPDATE = "force_upd_setup_v0.x.x-v0.6.2" + const val KEY_MEDIA_PIPE_MODEL_ID = "key_mediapipe_model_id" + const val KEY_QNN_MODEL_ID = "key_qnn_model_id" + const val KEY_QNN_RUN_ON_CPU = "key_qnn_run_on_cpu" + const val KEY_QNN_USE_OPENCL = "key_qnn_use_opencl" + const val KEY_QNN_SCHEDULER = "key_qnn_scheduler" + const val KEY_QNN_SHOW_DIFFUSION_PROCESS = "key_qnn_show_diffusion_process" + const val KEY_QNN_LAST_PROMPT = "key_qnn_last_prompt" + const val KEY_QNN_LAST_NEGATIVE_PROMPT = "key_qnn_last_negative_prompt" + const val KEY_ONNX_LAST_PROMPT = "key_onnx_last_prompt" + const val KEY_ONNX_LAST_NEGATIVE_PROMPT = "key_onnx_last_negative_prompt" + const val KEY_MEDIAPIPE_LAST_PROMPT = "key_mediapipe_last_prompt" + const val KEY_MEDIAPIPE_LAST_NEGATIVE_PROMPT = "key_mediapipe_last_negative_prompt" + const val KEY_LOCAL_MODEL_ID = "key_local_model_id" + const val KEY_LOCAL_NN_API = "key_local_nn_api" + const val KEY_DESIGN_DYNAMIC_COLORS = "key_design_dynamic_colors" + const val KEY_DESIGN_SYSTEM_DARK_THEME = "key_design_system_dark_theme" + const val KEY_DESIGN_DARK_THEME = "key_design_dark_theme" + const val KEY_DESIGN_COLOR_TOKEN = "key_design_color_token_theme" + const val KEY_DESIGN_DARK_TOKEN = "key_design_dark_color_token_theme" + const val KEY_BACKGROUND_GENERATION = "key_background_generation" + const val KEY_BACKGROUND_PROCESS_COUNT = "key_background_process_count" + const val KEY_GALLERY_GRID = "key_gallery_grid" + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/preference/SessionPreferenceImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/preference/SessionPreferenceImpl.kt new file mode 100644 index 000000000..94df8fc70 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/preference/SessionPreferenceImpl.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.data.preference + +import dev.minios.pdaiv1.domain.preference.SessionPreference + +class SessionPreferenceImpl : SessionPreference { + + private var _swarmUiSessionId: String = "" + + override var swarmUiSessionId: String + get() = _swarmUiSessionId + set(value) { + _swarmUiSessionId = value + } +} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/provider/ServerUrlProvider.kt b/data/src/main/java/dev/minios/pdaiv1/data/provider/ServerUrlProvider.kt similarity index 75% rename from data/src/main/java/com/shifthackz/aisdv1/data/provider/ServerUrlProvider.kt rename to data/src/main/java/dev/minios/pdaiv1/data/provider/ServerUrlProvider.kt index 33ff94186..d8a206477 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/provider/ServerUrlProvider.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/provider/ServerUrlProvider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.data.provider +package dev.minios.pdaiv1.data.provider import io.reactivex.rxjava3.core.Single diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/DownloadableModelRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/DownloadableModelRemoteDataSource.kt new file mode 100644 index 000000000..6c18b24e0 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/DownloadableModelRemoteDataSource.kt @@ -0,0 +1,74 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.file.unzip +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.network.api.pdai.DownloadableModelsApi +import dev.minios.pdaiv1.network.response.DownloadableModelResponse +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import java.io.File + +internal class DownloadableModelRemoteDataSource( + private val api: DownloadableModelsApi, + private val fileProviderDescriptor: FileProviderDescriptor, +) : DownloadableModelDataSource.Remote { + + override fun fetch(): Single> = Single.zip( + api + .fetchOnnxModels() + .map { it.mapRawToCheckpointDomain(LocalAiModel.Type.ONNX) }, + api + .fetchMediaPipeModels() + .map { it.mapRawToCheckpointDomain(LocalAiModel.Type.MediaPipe) }, + api + .fetchQnnModels() + .onErrorReturn { emptyList() } + .map { it.mapRawToCheckpointDomain(LocalAiModel.Type.QNN) }, + ::Triple, + ) + .map { (onnx, mediapipe, qnn) -> listOf(onnx, mediapipe, qnn).flatten() } + + override fun download(id: String, url: String): Observable = Completable + .fromAction { + val dir = File("${fileProviderDescriptor.localModelDirPath}/${id}") + val destination = File(getDestinationPath(id)) + if (destination.exists()) destination.delete() + if (!dir.exists()) dir.mkdirs() + } + .andThen( + api.downloadModel( + remoteUrl = url, + localPath = getDestinationPath(id), + stateProgress = DownloadState::Downloading, + stateComplete = DownloadState::Complete, + stateFailed = DownloadState::Error, + ) + ) + .flatMap { state -> + val chain = Observable.just(state) + if (state is DownloadState.Complete) { + Completable + .create { emitter -> + try { + state.file.unzip() + emitter.onComplete() + } catch (e: Exception) { + emitter.onError(e) + } + } + .andThen(Completable.fromAction { File(getDestinationPath(id)).delete() }) + .andThen(chain) + } else { + chain + } + } + + private fun getDestinationPath(id: String): String { + return "${fileProviderDescriptor.localModelDirPath}/${id}/model.zip" + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/FalAiEndpointRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/FalAiEndpointRemoteDataSource.kt new file mode 100644 index 000000000..7a6bd01fb --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/FalAiEndpointRemoteDataSource.kt @@ -0,0 +1,34 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.FalAiOpenApiParser +import dev.minios.pdaiv1.domain.datasource.FalAiEndpointDataSource +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import io.reactivex.rxjava3.core.Single +import okhttp3.OkHttpClient +import okhttp3.Request + +/** + * Fetches fal.ai endpoint schemas from remote URLs. + */ +internal class FalAiEndpointRemoteDataSource( + private val httpClient: OkHttpClient, +) : FalAiEndpointDataSource.Remote { + + override fun fetchFromUrl(url: String): Single = Single.fromCallable { + val request = Request.Builder() + .url(url) + .get() + .build() + + val response = httpClient.newCall(request).execute() + + if (!response.isSuccessful) { + throw IllegalStateException("Failed to fetch OpenAPI schema: ${response.code}") + } + + val json = response.body?.string() + ?: throw IllegalStateException("Empty response body") + + FalAiOpenApiParser.parse(json, isCustom = true) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/FalAiGenerationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/FalAiGenerationRemoteDataSource.kt new file mode 100644 index 000000000..bf6ceea9e --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/FalAiGenerationRemoteDataSource.kt @@ -0,0 +1,490 @@ +package dev.minios.pdaiv1.data.remote + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.datasource.FalAiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiEndpointCategory +import dev.minios.pdaiv1.domain.entity.MediaType +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.network.api.falai.FalAiApi +import dev.minios.pdaiv1.network.request.FalAiImageSize +import dev.minios.pdaiv1.network.request.FalAiTextToImageRequest +import dev.minios.pdaiv1.network.response.FalAiImage +import dev.minios.pdaiv1.network.response.FalAiQueueResponse +import io.reactivex.rxjava3.core.Single +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.ByteArrayOutputStream +import java.util.Date +import java.util.concurrent.TimeUnit + +internal class FalAiGenerationRemoteDataSource( + private val api: FalAiApi, + private val httpClient: OkHttpClient, + private val mediaFileManager: MediaFileManager, +) : FalAiGenerationDataSource.Remote { + + override fun validateApiKey(): Single = api + .listModels(limit = 1) + .map { true } + .onErrorResumeNext { t -> + errorLog(t) + Single.just(false) + } + + override fun textToImage(model: String, payload: TextToImagePayload): Single { + val request = payload.toFalAiRequest() + val url = "$BASE_URL$model" + debugLog("FalAi textToImage: url=$url, request=$request") + + return api.submitToQueue(url, request) + .doOnError { t -> errorLog("FalAi submitToQueue error: ${t.message}") } + .flatMap { queueResponse -> handleQueueResponse(queueResponse, payload) } + } + + private fun handleQueueResponse( + response: FalAiQueueResponse, + payload: TextToImagePayload, + ): Single { + // If response already contains images (sync mode or fast completion) + response.images?.firstOrNull()?.let { image -> + return downloadAndConvertToBase64(image.url) + .map { base64 -> createResult(payload, base64) } + } + + // Otherwise, poll for completion + val statusUrl = response.statusUrl + ?: return Single.error(IllegalStateException("No status URL returned from fal.ai")) + val resultUrl = response.responseUrl + ?: return Single.error(IllegalStateException("No response URL returned from fal.ai")) + + return pollForCompletion(statusUrl, resultUrl, payload) + } + + private fun pollForCompletion( + statusUrl: String, + resultUrl: String, + payload: TextToImagePayload, + ): Single { + debugLog("FalAi pollForCompletion: checking status at $statusUrl") + return api.checkStatus(statusUrl) + .doOnSuccess { status -> debugLog("FalAi status response: ${status.status}") } + .doOnError { t -> errorLog("FalAi checkStatus error: ${t.message}") } + .flatMap { status -> + when (status.status) { + "COMPLETED" -> { + debugLog("FalAi generation completed, fetching result from $resultUrl") + fetchAndProcessResult(resultUrl, payload) + } + "FAILED" -> Single.error(IllegalStateException("fal.ai generation failed")) + "IN_QUEUE", "IN_PROGRESS" -> { + debugLog("FalAi status: ${status.status}, queue position: ${status.queuePosition}") + // Wait and retry + Single.timer(POLL_INTERVAL_MS, TimeUnit.MILLISECONDS) + .flatMap { pollForCompletion(statusUrl, resultUrl, payload) } + } + else -> Single.error(IllegalStateException("Unknown status: ${status.status}")) + } + } + } + + private fun fetchAndProcessResult( + resultUrl: String, + payload: TextToImagePayload, + ): Single { + debugLog("FalAi fetchResult: fetching from $resultUrl") + return api.fetchResult(resultUrl) + .doOnSuccess { result -> debugLog("FalAi fetchResult response: images=${result.images?.size}, video=${result.video != null}") } + .doOnError { t -> errorLog("FalAi fetchResult error: ${t.message}") } + .flatMap { result -> + val imageUrl = result.images?.firstOrNull()?.url + ?: return@flatMap Single.error( + IllegalStateException("No images in fal.ai response") + ) + downloadAndConvertToBase64(imageUrl) + .map { base64 -> createResult(payload, base64) } + } + } + + private fun downloadAndSaveMedia(url: String, mediaType: MediaType): Single { + return Single.fromCallable { + val request = Request.Builder().url(url).build() + val response = httpClient.newCall(request).execute() + + if (!response.isSuccessful) { + throw IllegalStateException("Failed to download media: ${response.code}") + } + + val bytes = response.body?.bytes() + ?: throw IllegalStateException("Empty response body") + + when (mediaType) { + MediaType.IMAGE -> { + val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + ?: throw IllegalStateException("Failed to decode image") + + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + mediaFileManager.saveMedia(outputStream.toByteArray(), MediaType.IMAGE) + } + MediaType.VIDEO -> { + mediaFileManager.saveMedia(bytes, MediaType.VIDEO) + } + } + } + } + + private fun downloadAndConvertToBase64(imageUrl: String): Single { + return Single.fromCallable { + debugLog("FalAi downloadAndConvertToBase64: starting download from $imageUrl") + val request = Request.Builder().url(imageUrl).build() + val response = httpClient.newCall(request).execute() + debugLog("FalAi downloadAndConvertToBase64: response code=${response.code}") + + if (!response.isSuccessful) { + throw IllegalStateException("Failed to download image: ${response.code}") + } + + val bytes = response.body?.bytes() + ?: throw IllegalStateException("Empty response body") + debugLog("FalAi downloadAndConvertToBase64: downloaded ${bytes.size} bytes") + + val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + ?: throw IllegalStateException("Failed to decode image") + debugLog("FalAi downloadAndConvertToBase64: decoded bitmap ${bitmap.width}x${bitmap.height}") + + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + val base64 = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP) + debugLog("FalAi downloadAndConvertToBase64: converted to base64, length=${base64.length}") + base64 + } + } + + private fun createResult(payload: TextToImagePayload, base64: String): AiGenerationResult { + return AiGenerationResult( + id = 0L, + image = base64, + inputImage = "", + createdAt = Date(), + type = AiGenerationResult.Type.TEXT_TO_IMAGE, + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + samplingSteps = payload.samplingSteps, + cfgScale = payload.cfgScale, + restoreFaces = false, + sampler = payload.sampler, + seed = payload.seed, + subSeed = "", + subSeedStrength = 0f, + denoisingStrength = 0f, + hidden = false, + modelName = payload.modelName, + ) + } + + private fun TextToImagePayload.toFalAiRequest(): FalAiTextToImageRequest { + return FalAiTextToImageRequest( + prompt = prompt, + negativePrompt = negativePrompt.takeIf { it.isNotBlank() }, + imageSize = FalAiImageSize(width = width, height = height), + numInferenceSteps = samplingSteps, + guidanceScale = cfgScale, + seed = seed.toLongOrNull(), + numImages = 1, + enableSafetyChecker = false, + syncMode = false, + ) + } + + override fun generateDynamic( + endpoint: FalAiEndpoint, + parameters: Map, + ): Single> { + val url = "$BASE_URL${endpoint.endpointId}" + + // Filter out null values and add sync_mode = false + val requestBody = parameters + .filterValues { it != null } + .toMutableMap() + .apply { put("sync_mode", false) } + + debugLog("FalAi generateDynamic: url=$url, params=$requestBody") + + return api.submitDynamicToQueue(url, requestBody) + .doOnSuccess { queueResponse -> + debugLog("FalAi submitDynamicToQueue response: requestId=${queueResponse.requestId}, statusUrl=${queueResponse.statusUrl}, images=${queueResponse.images?.size}, seed=${queueResponse.seed}") + } + .doOnError { t -> errorLog("FalAi submitDynamicToQueue error: ${t.message}") } + .flatMap { queueResponse -> handleDynamicQueueResponse(queueResponse, endpoint, parameters) } + } + + private fun handleDynamicQueueResponse( + response: FalAiQueueResponse, + endpoint: FalAiEndpoint, + parameters: Map, + ): Single> { + // If response already contains images (sync mode or fast completion) + val images = response.images + if (!images.isNullOrEmpty()) { + return processImages(images, endpoint, parameters, response.seed, response.prompt) + } + + // If response contains video (for video generation endpoints) + response.video?.let { video -> + return processVideo(video, endpoint, parameters, response.seed, response.prompt) + } + + // Otherwise, poll for completion + val statusUrl = response.statusUrl + ?: return Single.error(IllegalStateException("No status URL returned from fal.ai")) + val resultUrl = response.responseUrl + ?: return Single.error(IllegalStateException("No response URL returned from fal.ai")) + + return pollForDynamicCompletion(statusUrl, resultUrl, endpoint, parameters) + } + + private fun pollForDynamicCompletion( + statusUrl: String, + resultUrl: String, + endpoint: FalAiEndpoint, + parameters: Map, + ): Single> { + debugLog("FalAi pollForDynamicCompletion: checking status at $statusUrl") + return api.checkStatus(statusUrl) + .doOnSuccess { status -> debugLog("FalAi dynamic status response: ${status.status}") } + .doOnError { t -> errorLog("FalAi dynamic checkStatus error: ${t.message}") } + .flatMap { status -> + when (status.status) { + "COMPLETED" -> { + debugLog("FalAi dynamic generation completed, fetching result from $resultUrl") + fetchAndProcessDynamicResult(resultUrl, endpoint, parameters) + } + "FAILED" -> Single.error(IllegalStateException("fal.ai generation failed")) + "IN_QUEUE", "IN_PROGRESS" -> { + debugLog("FalAi dynamic status: ${status.status}, queue position: ${status.queuePosition}") + Single.timer(POLL_INTERVAL_MS, TimeUnit.MILLISECONDS) + .flatMap { pollForDynamicCompletion(statusUrl, resultUrl, endpoint, parameters) } + } + else -> Single.error(IllegalStateException("Unknown status: ${status.status}")) + } + } + } + + private fun fetchAndProcessDynamicResult( + resultUrl: String, + endpoint: FalAiEndpoint, + parameters: Map, + ): Single> { + debugLog("FalAi fetchDynamicResult: fetching from $resultUrl") + return api.fetchResult(resultUrl) + .doOnSuccess { result -> debugLog("FalAi fetchDynamicResult response: images=${result.images?.size}, video=${result.video != null}") } + .doOnError { t -> errorLog("FalAi fetchDynamicResult error: ${t.message}") } + .flatMap { result -> + // Check for images first + val images = result.images + if (!images.isNullOrEmpty()) { + return@flatMap processImages(images, endpoint, parameters, result.seed, result.prompt) + } + + // Check for video + result.video?.let { video -> + return@flatMap processVideo(video, endpoint, parameters, result.seed, result.prompt) + } + + // No images or video found + Single.error>( + IllegalStateException("No images or video in fal.ai response") + ) + } + } + + private fun processImages( + images: List, + endpoint: FalAiEndpoint, + parameters: Map, + responseSeed: String? = null, + responsePrompt: String? = null, + ): Single> { + debugLog("FalAi processImages: starting to process ${images.size} images") + return io.reactivex.rxjava3.core.Observable.fromIterable(images) + .flatMapSingle { image -> + debugLog("FalAi processImages: downloading image from ${image.url}") + downloadAndConvertToBase64(image.url) + .doOnSuccess { debugLog("FalAi processImages: downloaded and converted image, base64 length=${it.length}") } + .doOnError { t -> errorLog("FalAi processImages: error downloading image: ${t.message}") } + .map { base64 -> + createDynamicImageResult( + endpoint = endpoint, + parameters = parameters, + base64 = base64, + responseSeed = responseSeed, + responsePrompt = responsePrompt, + imageWidth = image.width, + imageHeight = image.height, + ) + } + } + .toList() + } + + private fun processVideo( + video: FalAiImage, + endpoint: FalAiEndpoint, + parameters: Map, + responseSeed: String? = null, + responsePrompt: String? = null, + ): Single> { + return downloadAndSaveMedia(video.url, MediaType.VIDEO) + .map { mediaPath -> + createDynamicResult( + endpoint = endpoint, + parameters = parameters, + mediaPath = mediaPath, + mediaType = MediaType.VIDEO, + responseSeed = responseSeed, + responsePrompt = responsePrompt, + imageWidth = video.width, + imageHeight = video.height, + ) + } + .map { listOf(it) } + } + + private fun createDynamicImageResult( + endpoint: FalAiEndpoint, + parameters: Map, + base64: String, + responseSeed: String? = null, + responsePrompt: String? = null, + imageWidth: Int? = null, + imageHeight: Int? = null, + ): AiGenerationResult { + val prompt = responsePrompt ?: parameters["prompt"]?.toString() ?: "" + val negativePrompt = parameters["negative_prompt"]?.toString() ?: "" + val width = imageWidth ?: extractWidth(parameters) + val height = imageHeight ?: extractHeight(parameters) + val steps = (parameters["num_inference_steps"] as? Number)?.toInt() ?: 28 + val guidance = (parameters["guidance_scale"] as? Number)?.toFloat() ?: 3.5f + val seed = responseSeed ?: parameters["seed"]?.toString() ?: "" + + val generationType = when (endpoint.category) { + FalAiEndpointCategory.IMAGE_TO_IMAGE, + FalAiEndpointCategory.INPAINTING -> AiGenerationResult.Type.IMAGE_TO_IMAGE + else -> AiGenerationResult.Type.TEXT_TO_IMAGE + } + + return AiGenerationResult( + id = 0L, + image = base64, + inputImage = "", + createdAt = Date(), + type = generationType, + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + samplingSteps = steps, + cfgScale = guidance, + restoreFaces = false, + sampler = "fal.ai/${endpoint.endpointId}", + seed = seed, + subSeed = "", + subSeedStrength = 0f, + denoisingStrength = 0f, + hidden = false, + modelName = endpoint.endpointId, + ) + } + + private fun createDynamicResult( + endpoint: FalAiEndpoint, + parameters: Map, + mediaPath: String, + mediaType: MediaType, + responseSeed: String? = null, + responsePrompt: String? = null, + imageWidth: Int? = null, + imageHeight: Int? = null, + ): AiGenerationResult { + val prompt = responsePrompt ?: parameters["prompt"]?.toString() ?: "" + val negativePrompt = parameters["negative_prompt"]?.toString() ?: "" + val width = imageWidth ?: extractWidth(parameters) + val height = imageHeight ?: extractHeight(parameters) + val steps = (parameters["num_inference_steps"] as? Number)?.toInt() ?: 28 + val guidance = (parameters["guidance_scale"] as? Number)?.toFloat() ?: 3.5f + val seed = responseSeed ?: parameters["seed"]?.toString() ?: "" + + val generationType = when (endpoint.category) { + FalAiEndpointCategory.IMAGE_TO_IMAGE, + FalAiEndpointCategory.INPAINTING -> AiGenerationResult.Type.IMAGE_TO_IMAGE + else -> AiGenerationResult.Type.TEXT_TO_IMAGE + } + + return AiGenerationResult( + id = 0L, + image = "", + inputImage = "", + createdAt = Date(), + type = generationType, + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + samplingSteps = steps, + cfgScale = guidance, + restoreFaces = false, + sampler = "fal.ai/${endpoint.endpointId}", + seed = seed, + subSeed = "", + subSeedStrength = 0f, + denoisingStrength = 0f, + hidden = false, + mediaPath = mediaPath, + inputMediaPath = "", + mediaType = mediaType, + modelName = endpoint.endpointId, + ) + } + + private fun extractWidth(parameters: Map): Int { + val imageSize = parameters["image_size"] + return when (imageSize) { + is Map<*, *> -> (imageSize["width"] as? Number)?.toInt() ?: 1024 + is String -> parseImageSizeDimension(imageSize, true) + else -> 1024 + } + } + + private fun extractHeight(parameters: Map): Int { + val imageSize = parameters["image_size"] + return when (imageSize) { + is Map<*, *> -> (imageSize["height"] as? Number)?.toInt() ?: 1024 + is String -> parseImageSizeDimension(imageSize, false) + else -> 1024 + } + } + + private fun parseImageSizeDimension(sizeStr: String, isWidth: Boolean): Int { + return if (sizeStr.contains("x")) { + val parts = sizeStr.split("x") + (if (isWidth) parts.getOrNull(0) else parts.getOrNull(1)) + ?.toIntOrNull() ?: 1024 + } else { + 1024 + } + } + + companion object { + private const val BASE_URL = "https://queue.fal.run/" + private const val POLL_INTERVAL_MS = 2000L + } +} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/HordeGenerationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/HordeGenerationRemoteDataSource.kt similarity index 86% rename from data/src/main/java/com/shifthackz/aisdv1/data/remote/HordeGenerationRemoteDataSource.kt rename to data/src/main/java/dev/minios/pdaiv1/data/remote/HordeGenerationRemoteDataSource.kt index 86b447da2..5ef2e6657 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/HordeGenerationRemoteDataSource.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/HordeGenerationRemoteDataSource.kt @@ -1,16 +1,16 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote import android.graphics.BitmapFactory -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.mappers.mapCloudToAiGenResult -import com.shifthackz.aisdv1.data.mappers.mapToHordeRequest -import com.shifthackz.aisdv1.domain.datasource.HordeGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.network.api.horde.HordeRestApi -import com.shifthackz.aisdv1.network.request.HordeGenerationAsyncRequest +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.data.mappers.mapCloudToAiGenResult +import dev.minios.pdaiv1.data.mappers.mapToHordeRequest +import dev.minios.pdaiv1.domain.datasource.HordeGenerationDataSource +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.network.api.horde.HordeRestApi +import dev.minios.pdaiv1.network.request.HordeGenerationAsyncRequest import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/HuggingFaceGenerationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/HuggingFaceGenerationRemoteDataSource.kt new file mode 100644 index 000000000..41b74918c --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/HuggingFaceGenerationRemoteDataSource.kt @@ -0,0 +1,51 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.data.mappers.mapCloudToAiGenResult +import dev.minios.pdaiv1.data.mappers.mapToHuggingFaceRequest +import dev.minios.pdaiv1.domain.datasource.HuggingFaceGenerationDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.network.api.huggingface.HuggingFaceApi +import dev.minios.pdaiv1.network.api.huggingface.HuggingFaceInferenceApi +import io.reactivex.rxjava3.core.Single + +internal class HuggingFaceGenerationRemoteDataSource( + private val huggingFaceApi: HuggingFaceApi, + private val huggingFaceInferenceApi: HuggingFaceInferenceApi, + private val converter: BitmapToBase64Converter, +) : HuggingFaceGenerationDataSource.Remote { + + override fun validateApiKey(): Single = huggingFaceApi + .validateBearerToken() + .andThen(Single.just(true)) + .onErrorResumeNext { t -> + errorLog(t) + Single.just(false) + } + + override fun textToImage( + modelName: String, + payload: TextToImagePayload, + ): Single = huggingFaceInferenceApi + .generate(modelName, payload.mapToHuggingFaceRequest()) + .map(BitmapToBase64Converter::Input) + .flatMap(converter::invoke) + .map(BitmapToBase64Converter.Output::base64ImageString) + .map { base64 -> payload to base64 } + .map(Pair::mapCloudToAiGenResult) + + override fun imageToImage( + modelName: String, + payload: ImageToImagePayload, + ): Single = huggingFaceInferenceApi + .generate(modelName, payload.mapToHuggingFaceRequest()) + .map(BitmapToBase64Converter::Input) + .flatMap(converter::invoke) + .map(BitmapToBase64Converter.Output::base64ImageString) + .map { base64 -> payload to base64 } + .map(Pair::mapCloudToAiGenResult) + +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/HuggingFaceModelsRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/HuggingFaceModelsRemoteDataSource.kt new file mode 100644 index 000000000..df17ed594 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/HuggingFaceModelsRemoteDataSource.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.domain.datasource.HuggingFaceModelsDataSource +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.network.api.pdai.HuggingFaceModelsApi +import dev.minios.pdaiv1.network.model.HuggingFaceModelRaw + +internal class HuggingFaceModelsRemoteDataSource( + private val api: HuggingFaceModelsApi, +) : HuggingFaceModelsDataSource.Remote { + + override fun fetchHuggingFaceModels() = api + .fetchHuggingFaceModels() + .map(List::mapRawToCheckpointDomain) + .onErrorReturn { listOf(HuggingFaceModel.default) } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/OpenAiGenerationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/OpenAiGenerationRemoteDataSource.kt new file mode 100644 index 000000000..800bafd33 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/OpenAiGenerationRemoteDataSource.kt @@ -0,0 +1,35 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.data.mappers.mapCloudToAiGenResult +import dev.minios.pdaiv1.data.mappers.mapToOpenAiRequest +import dev.minios.pdaiv1.domain.datasource.OpenAiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.network.api.openai.OpenAiApi +import io.reactivex.rxjava3.core.Single +import java.lang.IllegalStateException + +internal class OpenAiGenerationRemoteDataSource( + private val api: OpenAiApi, +) : OpenAiGenerationDataSource.Remote { + + override fun validateApiKey() = api + .validateBearerToken() + .andThen(Single.just(true)) + .onErrorResumeNext { t -> + errorLog(t) + Single.just(false) + } + + override fun textToImage(payload: TextToImagePayload) = payload + .mapToOpenAiRequest() + .let(api::generateImage) + .flatMap { response -> + response.data?.firstOrNull()?.b64json?.let { base64 -> + Single.just(payload to base64) + } ?: run { + Single.error(IllegalStateException("Got null data object from API.")) + } + } + .map(Pair::mapCloudToAiGenResult) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/RandomImageRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/RandomImageRemoteDataSource.kt new file mode 100644 index 000000000..266d45061 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/RandomImageRemoteDataSource.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.domain.datasource.RandomImageDataSource +import dev.minios.pdaiv1.network.api.imagecdn.ImageCdnRestApi + +internal class RandomImageRemoteDataSource( + private val api: ImageCdnRestApi, +) : RandomImageDataSource.Remote { + + override fun fetch() = api.fetchRandomImage() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/ReportRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/ReportRemoteDataSource.kt new file mode 100644 index 000000000..aa8435089 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/ReportRemoteDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.domain.datasource.ReportDataSource +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.network.api.pdai.ReportApi +import dev.minios.pdaiv1.network.request.ReportRequest +import io.reactivex.rxjava3.core.Completable + +internal class ReportRemoteDataSource(private val api: ReportApi) : ReportDataSource.Remote { + + override fun send( + text: String, + reason: ReportReason, + image: String, + source: String, + model: String + ): Completable { + val payload = ReportRequest(text, reason.toString(), image, source, model) + return api.postReport(payload) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/ServerConfigurationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/ServerConfigurationRemoteDataSource.kt new file mode 100755 index 000000000..2e465c50f --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/ServerConfigurationRemoteDataSource.kt @@ -0,0 +1,26 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapToDomain +import dev.minios.pdaiv1.data.mappers.mapToRequest +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.ServerConfigurationDataSource +import dev.minios.pdaiv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_SD_OPTIONS +import dev.minios.pdaiv1.network.model.ServerConfigurationRaw + +internal class ServerConfigurationRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : ServerConfigurationDataSource.Remote { + + override fun fetchConfiguration() = serverUrlProvider(PATH_SD_OPTIONS) + .flatMap(api::fetchConfiguration) + .map(ServerConfigurationRaw::mapToDomain) + + override fun updateConfiguration(configuration: ServerConfiguration) = + serverUrlProvider(PATH_SD_OPTIONS) + .flatMapCompletable { url -> + api.updateConfiguration(url, configuration.mapToRequest()) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiCreditsRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiCreditsRemoteDataSource.kt new file mode 100644 index 000000000..7b411e82e --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiCreditsRemoteDataSource.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.network.api.stabilityai.StabilityAiApi +import io.reactivex.rxjava3.core.Single + +internal class StabilityAiCreditsRemoteDataSource( + private val api: StabilityAiApi, +) : StabilityAiCreditsDataSource.Remote { + + override fun fetch(): Single = api + .fetchCredits() + .map { it.credits ?: 0f } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiEnginesRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiEnginesRemoteDataSource.kt new file mode 100644 index 000000000..845ccaa49 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiEnginesRemoteDataSource.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.domain.datasource.StabilityAiEnginesDataSource +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine +import dev.minios.pdaiv1.network.api.stabilityai.StabilityAiApi +import dev.minios.pdaiv1.network.model.StabilityAiEngineRaw +import io.reactivex.rxjava3.core.Single + +internal class StabilityAiEnginesRemoteDataSource( + private val api: StabilityAiApi, +) : StabilityAiEnginesDataSource.Remote { + + override fun fetch(): Single> = api + .fetchEngines() + .map(List::mapRawToCheckpointDomain) +} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiGenerationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiGenerationRemoteDataSource.kt similarity index 77% rename from data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiGenerationRemoteDataSource.kt rename to data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiGenerationRemoteDataSource.kt index b3f3d9e59..9572f1170 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/remote/StabilityAiGenerationRemoteDataSource.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StabilityAiGenerationRemoteDataSource.kt @@ -1,15 +1,15 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.data.mappers.mapCloudToAiGenResult -import com.shifthackz.aisdv1.data.mappers.mapToStabilityAiRequest -import com.shifthackz.aisdv1.domain.datasource.StabilityAiGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.network.api.stabilityai.StabilityAiApi -import com.shifthackz.aisdv1.network.error.StabilityAiErrorMapper -import com.shifthackz.aisdv1.network.response.StabilityGenerationResponse +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.data.mappers.mapCloudToAiGenResult +import dev.minios.pdaiv1.data.mappers.mapToStabilityAiRequest +import dev.minios.pdaiv1.domain.datasource.StabilityAiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.network.api.stabilityai.StabilityAiApi +import dev.minios.pdaiv1.network.error.StabilityAiErrorMapper +import dev.minios.pdaiv1.network.response.StabilityGenerationResponse import io.reactivex.rxjava3.core.Single import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionEmbeddingsRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionEmbeddingsRemoteDataSource.kt new file mode 100644 index 000000000..ebafabb71 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionEmbeddingsRemoteDataSource.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.EmbeddingsDataSource +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_EMBEDDINGS +import dev.minios.pdaiv1.network.response.SdEmbeddingsResponse + +internal class StableDiffusionEmbeddingsRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : EmbeddingsDataSource.Remote.Automatic1111 { + + override fun fetchEmbeddings() = serverUrlProvider(PATH_EMBEDDINGS) + .flatMap(api::fetchEmbeddings) + .map(SdEmbeddingsResponse::mapRawToCheckpointDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionGenerationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionGenerationRemoteDataSource.kt new file mode 100755 index 000000000..79f6329b8 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionGenerationRemoteDataSource.kt @@ -0,0 +1,37 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.core.common.extensions.fixUrlSlashes +import dev.minios.pdaiv1.data.mappers.mapToAiGenResult +import dev.minios.pdaiv1.data.mappers.mapToRequest +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.StableDiffusionGenerationDataSource +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_IMG_TO_IMG +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_TXT_TO_IMG +import dev.minios.pdaiv1.network.response.SdGenerationResponse + +internal class StableDiffusionGenerationRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : StableDiffusionGenerationDataSource.Remote { + + override fun checkAvailability() = serverUrlProvider("/") + .flatMapCompletable(api::healthCheck) + + override fun checkAvailability(url: String) = api.healthCheck(url.fixUrlSlashes()) + + override fun textToImage(payload: TextToImagePayload) = serverUrlProvider(PATH_TXT_TO_IMG) + .flatMap { url -> api.textToImage(url, payload.mapToRequest()) } + .map { response -> payload to response } + .map(Pair::mapToAiGenResult) + + override fun imageToImage(payload: ImageToImagePayload) = serverUrlProvider(PATH_IMG_TO_IMG) + .flatMap { url -> api.imageToImage(url, payload.mapToRequest()) } + .map { response -> payload to response } + .map(Pair::mapToAiGenResult) + + override fun interruptGeneration() = serverUrlProvider(Automatic1111RestApi.PATH_INTERRUPT) + .flatMapCompletable(api::interrupt) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionHyperNetworksRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionHyperNetworksRemoteDataSource.kt new file mode 100644 index 000000000..35eabb040 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionHyperNetworksRemoteDataSource.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.StableDiffusionHyperNetworksDataSource +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_HYPER_NETWORKS +import dev.minios.pdaiv1.network.model.StableDiffusionHyperNetworkRaw + +internal class StableDiffusionHyperNetworksRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : StableDiffusionHyperNetworksDataSource.Remote { + + override fun fetchHyperNetworks() = serverUrlProvider(PATH_HYPER_NETWORKS) + .flatMap(api::fetchHyperNetworks) + .map(List::mapRawToCheckpointDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionLorasRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionLorasRemoteDataSource.kt new file mode 100644 index 000000000..917d6ed95 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionLorasRemoteDataSource.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapToDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.LorasDataSource +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_LORAS +import dev.minios.pdaiv1.network.model.StableDiffusionLoraRaw +import io.reactivex.rxjava3.core.Single + +internal class StableDiffusionLorasRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : LorasDataSource.Remote.Automatic1111 { + + override fun fetchLoras(): Single> = serverUrlProvider(PATH_LORAS) + .flatMap(api::fetchLoras) + .map(List::mapToDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionModelsRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionModelsRemoteDataSource.kt new file mode 100755 index 000000000..1dd9cec41 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionModelsRemoteDataSource.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.StableDiffusionModelsDataSource +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_SD_MODELS +import dev.minios.pdaiv1.network.model.StableDiffusionModelRaw + +internal class StableDiffusionModelsRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : StableDiffusionModelsDataSource.Remote { + + override fun fetchSdModels() = serverUrlProvider(PATH_SD_MODELS) + .flatMap(api::fetchSdModels) + .map(List::mapRawToCheckpointDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionSamplersRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionSamplersRemoteDataSource.kt new file mode 100755 index 000000000..f3be2257b --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/StableDiffusionSamplersRemoteDataSource.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.StableDiffusionSamplersDataSource +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_SAMPLERS +import dev.minios.pdaiv1.network.model.StableDiffusionSamplerRaw + +internal class StableDiffusionSamplersRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : StableDiffusionSamplersDataSource.Remote { + + override fun fetchSamplers() = serverUrlProvider(PATH_SAMPLERS) + .flatMap(api::fetchSamplers) + .map(List::mapRawToCheckpointDomain) +} \ No newline at end of file diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/SupportersRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/SupportersRemoteDataSource.kt new file mode 100644 index 000000000..804ac59a0 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/SupportersRemoteDataSource.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToDomain +import dev.minios.pdaiv1.domain.datasource.SupportersDataSource +import dev.minios.pdaiv1.domain.entity.Supporter +import dev.minios.pdaiv1.network.api.pdai.DonateApi +import dev.minios.pdaiv1.network.model.SupporterRaw +import io.reactivex.rxjava3.core.Single + +internal class SupportersRemoteDataSource( + private val api: DonateApi, +) : SupportersDataSource.Remote { + + override fun fetch(): Single> = api + .fetchSupporters() + .map(List::mapRawToDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiEmbeddingsRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiEmbeddingsRemoteDataSource.kt new file mode 100644 index 000000000..97ef97016 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiEmbeddingsRemoteDataSource.kt @@ -0,0 +1,29 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToEmbeddingDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.EmbeddingsDataSource +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi.Companion.PATH_MODELS +import dev.minios.pdaiv1.network.request.SwarmUiModelsRequest +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse +import io.reactivex.rxjava3.core.Single + +class SwarmUiEmbeddingsRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: SwarmUiApi, +) : EmbeddingsDataSource.Remote.SwarmUi { + + override fun fetchEmbeddings(sessionId: String): Single> = serverUrlProvider(PATH_MODELS) + .flatMap { url -> + val request = SwarmUiModelsRequest( + sessionId = sessionId, + subType = "Embedding", + path = "", + depth = 3, + ) + api.fetchModels(url, request) + } + .map(SwarmUiModelsResponse::mapRawToEmbeddingDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiGenerationRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiGenerationRemoteDataSource.kt new file mode 100644 index 000000000..a60224fff --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiGenerationRemoteDataSource.kt @@ -0,0 +1,75 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.core.common.extensions.fixUrlSlashes +import dev.minios.pdaiv1.core.imageprocessing.Base64EncodingConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.data.mappers.mapCloudToAiGenResult +import dev.minios.pdaiv1.data.mappers.mapToSwarmUiRequest +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.SwarmUiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi.Companion.PATH_GENERATE +import dev.minios.pdaiv1.network.request.SwarmUiGenerationRequest +import io.reactivex.rxjava3.core.Single + +class SwarmUiGenerationRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: SwarmUiApi, + private val bmpToBase64Converter: BitmapToBase64Converter, + private val base64EncodingConverter: Base64EncodingConverter, +) : SwarmUiGenerationDataSource.Remote { + + override fun textToImage( + sessionId: String, + model: String, + payload: TextToImagePayload + ): Single = + generate( + payload = payload, + request = payload.mapToSwarmUiRequest(sessionId, model), + ) + .map(Pair::mapCloudToAiGenResult) + + override fun imageToImage( + sessionId: String, + model: String, + payload: ImageToImagePayload, + ): Single = payload + .base64Image + .let(Base64EncodingConverter::Input) + .let(base64EncodingConverter::invoke) + .map(Base64EncodingConverter.Output::base64) + .map { base64 -> "data:image/png;base64,${base64}" } + .map { base64Uri -> payload.copy(base64Image = base64Uri) } + .flatMap { encodedPayload -> + generate( + payload = encodedPayload, + request = encodedPayload.mapToSwarmUiRequest(sessionId, model), + ) + } + .map { (_, outBase64) -> payload to outBase64 } + .map(Pair::mapCloudToAiGenResult) + + private fun generate( + payload: T, + request: SwarmUiGenerationRequest, + ): Single> = serverUrlProvider(PATH_GENERATE) + .flatMap { url -> api.generate(url, request) } + .flatMap { response -> + serverUrlProvider("").map { url -> response to url } + } + .flatMap { (response, url) -> + response.images + ?.firstOrNull() + ?.let { endpoint -> Single.just("$url/$endpoint".fixUrlSlashes()) } + ?: Single.error(IllegalStateException("Bad response")) + } + .flatMap(api::downloadImage) + .map(BitmapToBase64Converter::Input) + .flatMap(bmpToBase64Converter::invoke) + .map(BitmapToBase64Converter.Output::base64ImageString) + .map { base64 -> payload to base64 } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiLorasRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiLorasRemoteDataSource.kt new file mode 100644 index 000000000..951be546e --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiLorasRemoteDataSource.kt @@ -0,0 +1,29 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToLoraDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.LorasDataSource +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi.Companion.PATH_MODELS +import dev.minios.pdaiv1.network.request.SwarmUiModelsRequest +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse +import io.reactivex.rxjava3.core.Single + +internal class SwarmUiLorasRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: SwarmUiApi, +) : LorasDataSource.Remote.SwarmUi { + + override fun fetchLoras(sessionId: String): Single> = serverUrlProvider(PATH_MODELS) + .flatMap { url -> + val request = SwarmUiModelsRequest( + sessionId = sessionId, + subType = "LoRA", + path = "", + depth = 3, + ) + api.fetchModels(url, request) + } + .map(SwarmUiModelsResponse::mapRawToLoraDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiModelsRemoteDataSource.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiModelsRemoteDataSource.kt new file mode 100644 index 000000000..6d8861df2 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiModelsRemoteDataSource.kt @@ -0,0 +1,30 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.SwarmUiModelsDataSource +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi.Companion.PATH_MODELS +import dev.minios.pdaiv1.network.request.SwarmUiModelsRequest +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse +import io.reactivex.rxjava3.core.Single + +internal class SwarmUiModelsRemoteDataSource( + private val serverUrlProvider: ServerUrlProvider, + private val api: SwarmUiApi, +) : SwarmUiModelsDataSource.Remote { + + override fun fetchSwarmModels(sessionId: String): Single> = PATH_MODELS + .let(serverUrlProvider::invoke) + .flatMap { url -> + val request = SwarmUiModelsRequest( + sessionId = sessionId, + subType = "Stable-Diffusion", + path = "", + depth = 3, + ) + api.fetchModels(url, request) + } + .map(SwarmUiModelsResponse::mapRawToCheckpointDomain) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiSessionDataSourceImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiSessionDataSourceImpl.kt new file mode 100644 index 000000000..3fc32ac91 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/remote/SwarmUiSessionDataSourceImpl.kt @@ -0,0 +1,51 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.core.common.extensions.fixUrlSlashes +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.preference.SessionPreference +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi.Companion.PATH_SESSION +import dev.minios.pdaiv1.network.exception.BadSessionException +import io.reactivex.rxjava3.core.Single + +internal class SwarmUiSessionDataSourceImpl( + private val api: SwarmUiApi, + private val sessionPreference: SessionPreference, + private val serverUrlProvider: ServerUrlProvider, +) : SwarmUiSessionDataSource { + + override fun getSessionId(connectUrl: String?): Single = + if (sessionPreference.swarmUiSessionId.isBlank()) { + forceRenew(connectUrl) + } else { + Single.just(sessionPreference.swarmUiSessionId) + } + + override fun forceRenew(connectUrl: String?): Single { + val chain = connectUrl + ?.let { url -> "$url/$PATH_SESSION".fixUrlSlashes() } + ?.let(api::getNewSession) + ?: serverUrlProvider(PATH_SESSION).flatMap(api::getNewSession) + + return chain + .flatMap { response -> + response.sessionId + ?.takeIf(String::isNotBlank) + ?.let { Single.just(it) } + ?: Single.error(IllegalStateException("Bad session ID.")) + } + .map { sessionId -> + sessionPreference.swarmUiSessionId = sessionId + sessionId + } + } + + override fun handleSessionError(chain: Single): Single = chain.onErrorResumeNext { t -> + if (t is BadSessionException) { + forceRenew().flatMap { chain } + } else { + Single.error(t) + } + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/DownloadableModelRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/DownloadableModelRepositoryImpl.kt new file mode 100644 index 000000000..6f307a9f4 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/DownloadableModelRepositoryImpl.kt @@ -0,0 +1,46 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository +import io.reactivex.rxjava3.core.Single + +internal class DownloadableModelRepositoryImpl( + private val remoteDataSource: DownloadableModelDataSource.Remote, + private val localDataSource: DownloadableModelDataSource.Local, + private val buildInfoProvider: BuildInfoProvider, +) : DownloadableModelRepository { + + override fun download(id: String, url: String) = remoteDataSource.download(id, url) + + override fun delete(id: String) = localDataSource.delete(id) + + override fun getAllOnnx() = remoteDataSource + .fetch() + .flatMapCompletable(localDataSource::save) + .andThen(localDataSource.getAllOnnx()) + .onErrorResumeNext { localDataSource.getAllOnnx() } + + override fun getAllMediaPipe(): Single> { + if (buildInfoProvider.type == BuildType.FOSS) { + return Single.just(emptyList()) + } + return remoteDataSource + .fetch() + .flatMapCompletable(localDataSource::save) + .andThen(localDataSource.getAllMediaPipe()) + .onErrorResumeNext { localDataSource.getAllMediaPipe() } + } + + override fun getAllQnn(): Single> { + return remoteDataSource + .fetch() + .flatMapCompletable(localDataSource::save) + .andThen(localDataSource.getAllQnn()) + .onErrorResumeNext { localDataSource.getAllQnn() } + } + + override fun observeAllOnnx() = localDataSource.observeAllOnnx() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/EmbeddingsRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/EmbeddingsRepositoryImpl.kt new file mode 100644 index 000000000..8c9622094 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/EmbeddingsRepositoryImpl.kt @@ -0,0 +1,39 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.EmbeddingsDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.EmbeddingsRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class EmbeddingsRepositoryImpl( + private val rdsA1111: EmbeddingsDataSource.Remote.Automatic1111, + private val rdsSwarm: EmbeddingsDataSource.Remote.SwarmUi, + private val swarmSession: SwarmUiSessionDataSource, + private val lds: EmbeddingsDataSource.Local, + private val preferenceManager: PreferenceManager, +) : EmbeddingsRepository { + + override fun fetchEmbeddings(): Completable = when (preferenceManager.source) { + ServerSource.AUTOMATIC1111 -> rdsA1111 + .fetchEmbeddings() + .flatMapCompletable(lds::insertEmbeddings) + + ServerSource.SWARM_UI -> swarmSession + .getSessionId() + .flatMap(rdsSwarm::fetchEmbeddings) + .let(swarmSession::handleSessionError) + .flatMapCompletable(lds::insertEmbeddings) + + else -> Completable.complete() + } + + override fun fetchAndGetEmbeddings(): Single> = fetchEmbeddings() + .onErrorComplete() + .andThen(lds.getEmbeddings()) + + override fun getEmbeddings(): Single> = lds.getEmbeddings() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/FalAiEndpointRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/FalAiEndpointRepositoryImpl.kt new file mode 100644 index 000000000..fb1fa1df1 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/FalAiEndpointRepositoryImpl.kt @@ -0,0 +1,72 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.data.mappers.FalAiOpenApiParser +import dev.minios.pdaiv1.domain.datasource.FalAiEndpointDataSource +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +internal class FalAiEndpointRepositoryImpl( + private val builtInDataSource: FalAiEndpointDataSource.BuiltIn, + private val remoteDataSource: FalAiEndpointDataSource.Remote, + private val localDataSource: FalAiEndpointDataSource.Local, + private val preferenceManager: PreferenceManager, +) : FalAiEndpointRepository { + + override fun observeAll(): Observable> = Observable + .combineLatest( + builtInDataSource.getAll().toObservable(), + localDataSource.observeAll().startWithItem(emptyList()), + ) { builtIn, custom -> + builtIn + custom + } + + override fun getAll(): Single> = Single + .zip( + builtInDataSource.getAll(), + localDataSource.getAll().onErrorReturnItem(emptyList()), + ) { builtIn, custom -> + builtIn + custom + } + + override fun getById(id: String): Single = getAll() + .flatMap { endpoints -> + endpoints.find { it.endpointId == id || it.id == id } + ?.let { Single.just(it) } + ?: Single.error(NoSuchElementException("Endpoint not found: $id")) + } + + override fun getSelected(): Single { + val selectedId = preferenceManager.falAiSelectedEndpointId + return if (selectedId.isBlank()) { + // Return default endpoint (first built-in) + builtInDataSource.getAll().map { it.first() } + } else { + getById(selectedId).onErrorResumeNext { + // Fallback to first built-in if selected not found + builtInDataSource.getAll().map { it.first() } + } + } + } + + override fun setSelected(id: String): Completable = Completable.fromAction { + preferenceManager.falAiSelectedEndpointId = id + } + + override fun importFromJson(json: String): Single = Single + .fromCallable { FalAiOpenApiParser.parse(json, isCustom = true) } + .flatMap { endpoint -> + localDataSource.save(endpoint).andThen(Single.just(endpoint)) + } + + override fun importFromUrl(url: String): Single = remoteDataSource + .fetchFromUrl(url) + .flatMap { endpoint -> + localDataSource.save(endpoint).andThen(Single.just(endpoint)) + } + + override fun delete(id: String): Completable = localDataSource.delete(id) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/FalAiGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/FalAiGenerationRepositoryImpl.kt new file mode 100644 index 000000000..140dba0ae --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/FalAiGenerationRepositoryImpl.kt @@ -0,0 +1,27 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.FalAiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import io.reactivex.rxjava3.core.Single + +internal class FalAiGenerationRepositoryImpl( + private val remoteDataSource: FalAiGenerationDataSource.Remote, + private val endpointRepository: FalAiEndpointRepository, +) : FalAiGenerationRepository { + + override fun validateApiKey(): Single = remoteDataSource.validateApiKey() + + override fun generateFromText(payload: TextToImagePayload): Single = + endpointRepository.getSelected() + .map { endpoint -> endpoint.endpointId } + .flatMap { endpointId -> remoteDataSource.textToImage(endpointId, payload) } + + override fun generateDynamic( + endpoint: FalAiEndpoint, + parameters: Map, + ): Single> = remoteDataSource.generateDynamic(endpoint, parameters) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/ForgeModulesRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/ForgeModulesRepositoryImpl.kt new file mode 100644 index 000000000..8466954ec --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/ForgeModulesRepositoryImpl.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.data.mappers.mapRawToDomain +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.domain.repository.ForgeModulesRepository +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi.Companion.PATH_SD_MODULES +import dev.minios.pdaiv1.network.model.ForgeModuleRaw +import io.reactivex.rxjava3.core.Single + +internal class ForgeModulesRepositoryImpl( + private val serverUrlProvider: ServerUrlProvider, + private val api: Automatic1111RestApi, +) : ForgeModulesRepository { + + override fun fetchModules(): Single> = serverUrlProvider(PATH_SD_MODULES) + .flatMap(api::fetchForgeModules) + .map(List::mapRawToDomain) + .onErrorReturn { emptyList() } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/GenerationResultRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/GenerationResultRepositoryImpl.kt new file mode 100644 index 000000000..df513844a --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/GenerationResultRepositoryImpl.kt @@ -0,0 +1,197 @@ +package dev.minios.pdaiv1.data.repository + +import android.util.Base64 +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.data.core.CoreMediaStoreRepository +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.MediaType +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class GenerationResultRepositoryImpl( + preferenceManager: PreferenceManager, + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + private val localDataSource: GenerationResultDataSource.Local, + private val mediaFileManager: MediaFileManager, +) : CoreMediaStoreRepository( + preferenceManager, + mediaStoreGateway, + base64ToBitmapConverter, +), GenerationResultRepository { + + override fun getAll() = localDataSource.queryAll() + .map { results -> results.map { it.loadMediaFromFiles() } } + + override fun getAllIds() = localDataSource.queryAllIds() + + override fun getAllIdsWithBlurHash() = localDataSource.queryAllIdsWithBlurHash() + + override fun getThumbnailInfoByIds(idList: List) = localDataSource.queryThumbnailInfoByIdList(idList) + + override fun getPage(limit: Int, offset: Int) = localDataSource.queryPage(limit, offset) + .map { results -> results.map { it.loadMediaFromFiles() } } + + override fun getMediaStoreInfo() = getInfo() + + override fun getById(id: Long) = localDataSource.queryById(id) + .map { it.loadMediaFromFiles() } + + override fun getByIds(idList: List) = localDataSource.queryByIdList(idList) + .map { results -> results.map { it.loadMediaFromFiles() } } + + override fun getByIdsRaw(idList: List) = localDataSource.queryByIdList(idList) + + override fun insert(result: AiGenerationResult): Single { + val converted = result.saveMediaToFiles() + return localDataSource + .insert(converted) + .flatMap { id -> exportToMediaStore(result).andThen(Single.just(id)) } + } + + override fun deleteById(id: Long) = localDataSource.queryById(id) + .doOnSuccess { result -> + // Delete media files when deleting from gallery + if (result.mediaPath.isNotEmpty()) { + mediaFileManager.deleteMedia(result.mediaPath) + } + if (result.inputMediaPath.isNotEmpty()) { + mediaFileManager.deleteMedia(result.inputMediaPath) + } + } + .flatMapCompletable { localDataSource.deleteById(id) } + + override fun deleteByIdList(idList: List) = localDataSource.queryByIdList(idList) + .doOnSuccess { results -> + // Delete media files when deleting from gallery + results.forEach { result -> + if (result.mediaPath.isNotEmpty()) { + mediaFileManager.deleteMedia(result.mediaPath) + } + if (result.inputMediaPath.isNotEmpty()) { + mediaFileManager.deleteMedia(result.inputMediaPath) + } + } + } + .flatMapCompletable { localDataSource.deleteByIdList(idList) } + + override fun deleteAll() = localDataSource.queryAll() + .doOnSuccess { results -> + // Delete all media files + results.forEach { result -> + if (result.mediaPath.isNotEmpty()) { + mediaFileManager.deleteMedia(result.mediaPath) + } + if (result.inputMediaPath.isNotEmpty()) { + mediaFileManager.deleteMedia(result.inputMediaPath) + } + } + } + .flatMapCompletable { localDataSource.deleteAll() } + + override fun deleteAllUnliked(): Completable = localDataSource.deleteAllUnliked() + + override fun toggleVisibility(id: Long): Single = localDataSource + .queryById(id) + .map { it.copy(hidden = !it.hidden) } + .flatMap(localDataSource::insert) + .flatMap { localDataSource.queryById(id) } + .map(AiGenerationResult::hidden) + + override fun toggleLike(id: Long): Single = localDataSource + .queryById(id) + .map { it.copy(liked = !it.liked) } + .flatMap(localDataSource::insert) + .flatMap { localDataSource.queryById(id) } + .map(AiGenerationResult::liked) + + override fun likeByIds(idList: List): Completable = localDataSource.likeByIds(idList) + + override fun unlikeByIds(idList: List): Completable = localDataSource.unlikeByIds(idList) + + override fun hideByIds(idList: List): Completable = localDataSource.hideByIds(idList) + + override fun unhideByIds(idList: List): Completable = localDataSource.unhideByIds(idList) + + override fun migrateBase64ToFiles(): Completable = localDataSource.queryAll() + .flatMapCompletable { results -> + val needsMigration = results.filter { result -> + // Needs migration if image has base64 data but mediaPath is empty + (result.image.isNotEmpty() && !mediaFileManager.isFilePath(result.image) && result.mediaPath.isEmpty()) || + (result.inputImage.isNotEmpty() && !mediaFileManager.isFilePath(result.inputImage) && result.inputMediaPath.isEmpty()) + } + if (needsMigration.isEmpty()) { + Completable.complete() + } else { + Completable.concat(needsMigration.map { result -> + val migrated = result.saveMediaToFiles() + localDataSource.insert(migrated).ignoreElement() + }) + } + } + + /** + * Converts base64 data to files before saving to database. + * After this, image/inputImage will be empty, mediaPath/inputMediaPath will contain file paths. + */ + private fun AiGenerationResult.saveMediaToFiles(): AiGenerationResult { + var mediaPath = this.mediaPath + var inputMediaPath = this.inputMediaPath + + // Convert main image base64 to file + if (image.isNotEmpty() && !mediaFileManager.isFilePath(image) && !mediaFileManager.isVideoUrl(image)) { + mediaPath = mediaFileManager.migrateBase64ToFile(image, mediaType) + } + + // Convert input image base64 to file + if (inputImage.isNotEmpty() && !mediaFileManager.isFilePath(inputImage)) { + inputMediaPath = mediaFileManager.migrateBase64ToFile(inputImage, MediaType.IMAGE) + } + + return copy( + image = "", // Clear base64 from database + inputImage = "", // Clear base64 from database + mediaPath = mediaPath, + inputMediaPath = inputMediaPath, + ) + } + + /** + * Loads media from files into base64 fields for UI consumption. + */ + private fun AiGenerationResult.loadMediaFromFiles(): AiGenerationResult { + var loadedImage = image + var loadedInputImage = inputImage + + // Load main media from file if path is set and image is empty + if (mediaPath.isNotEmpty() && image.isEmpty()) { + if (mediaFileManager.isVideoUrl(mediaPath)) { + // For videos, keep the VIDEO_URL: format + loadedImage = mediaPath + } else if (mediaFileManager.isFilePath(mediaPath)) { + mediaFileManager.loadMedia(mediaPath)?.let { bytes -> + loadedImage = Base64.encodeToString(bytes, Base64.NO_WRAP) + } + } + } + + // Load input media from file if path is set and inputImage is empty + if (inputMediaPath.isNotEmpty() && inputImage.isEmpty()) { + if (mediaFileManager.isFilePath(inputMediaPath)) { + mediaFileManager.loadMedia(inputMediaPath)?.let { bytes -> + loadedInputImage = Base64.encodeToString(bytes, Base64.NO_WRAP) + } + } + } + + return copy( + image = loadedImage, + inputImage = loadedInputImage, + ) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/HordeGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/HordeGenerationRepositoryImpl.kt new file mode 100644 index 000000000..b213fbe3d --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/HordeGenerationRepositoryImpl.kt @@ -0,0 +1,49 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.HordeGenerationDataSource +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository + +internal class HordeGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + preferenceManager: PreferenceManager, + backgroundWorkObserver: BackgroundWorkObserver, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val remoteDataSource: HordeGenerationDataSource.Remote, + private val statusSource: HordeGenerationDataSource.StatusSource, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), HordeGenerationRepository { + + override fun observeStatus() = statusSource.observe() + + override fun validateApiKey() = remoteDataSource.validateApiKey() + + override fun generateFromText(payload: TextToImagePayload) = remoteDataSource + .textToImage(payload) + .flatMap(::insertGenerationResult) + + override fun generateFromImage(payload: ImageToImagePayload) = remoteDataSource + .imageToImage(payload) + .flatMap(::insertGenerationResult) + + override fun interruptGeneration() = remoteDataSource.interruptGeneration() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/HuggingFaceGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/HuggingFaceGenerationRepositoryImpl.kt new file mode 100644 index 000000000..3f7c84f7a --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/HuggingFaceGenerationRepositoryImpl.kt @@ -0,0 +1,44 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.HuggingFaceGenerationDataSource +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository + +internal class HuggingFaceGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + backgroundWorkObserver: BackgroundWorkObserver, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val preferenceManager: PreferenceManager, + private val remoteDataSource: HuggingFaceGenerationDataSource.Remote, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), HuggingFaceGenerationRepository { + + override fun validateApiKey() = remoteDataSource.validateApiKey() + + override fun generateFromText(payload: TextToImagePayload) = remoteDataSource + .textToImage(preferenceManager.huggingFaceModel, payload) + .flatMap(::insertGenerationResult) + + override fun generateFromImage(payload: ImageToImagePayload) = remoteDataSource + .imageToImage(preferenceManager.huggingFaceModel, payload) + .flatMap(::insertGenerationResult) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/HuggingFaceModelsRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/HuggingFaceModelsRepositoryImpl.kt new file mode 100644 index 000000000..c80efa8c5 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/HuggingFaceModelsRepositoryImpl.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.HuggingFaceModelsDataSource +import dev.minios.pdaiv1.domain.repository.HuggingFaceModelsRepository + +internal class HuggingFaceModelsRepositoryImpl( + private val remoteDataSource: HuggingFaceModelsDataSource.Remote, + private val localDataSource: HuggingFaceModelsDataSource.Local, +) : HuggingFaceModelsRepository { + + override fun fetchHuggingFaceModels() = remoteDataSource + .fetchHuggingFaceModels() + .concatMapCompletable(localDataSource::save) + + override fun fetchAndGetHuggingFaceModels() = fetchHuggingFaceModels() + .onErrorComplete() + .andThen(getHuggingFaceModels()) + + override fun getHuggingFaceModels() = localDataSource.getAll() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt new file mode 100644 index 000000000..a55bf3243 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt @@ -0,0 +1,62 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.data.mappers.mapLocalDiffusionToAiGenResult +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.diffusion.LocalDiffusion +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import io.reactivex.rxjava3.core.Single + +internal class LocalDiffusionGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + backgroundWorkObserver: BackgroundWorkObserver, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val preferenceManager: PreferenceManager, + private val localDiffusion: LocalDiffusion, + private val downloadableLocalDataSource: DownloadableModelDataSource.Local, + private val bitmapToBase64Converter: BitmapToBase64Converter, + private val schedulersProvider: SchedulersProvider, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), LocalDiffusionGenerationRepository { + + override fun observeStatus() = localDiffusion.observeStatus() + + override fun generateFromText(payload: TextToImagePayload) = downloadableLocalDataSource + .getSelectedOnnx() + .flatMap { model -> + if (model.downloaded) generate(payload) + else Single.error(IllegalStateException("Model not downloaded.")) + } + + override fun interruptGeneration() = localDiffusion.interrupt() + + private fun generate(payload: TextToImagePayload) = localDiffusion + .process(payload) + .subscribeOn(schedulersProvider.byToken(preferenceManager.localOnnxSchedulerThread)) + .map(BitmapToBase64Converter::Input) + .flatMap(bitmapToBase64Converter::invoke) + .map(BitmapToBase64Converter.Output::base64ImageString) + .map { base64 -> payload to base64 } + .map(Pair::mapLocalDiffusionToAiGenResult) + .flatMap(::insertGenerationResult) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/LorasRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/LorasRepositoryImpl.kt new file mode 100644 index 000000000..aa55f6df3 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/LorasRepositoryImpl.kt @@ -0,0 +1,39 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.LorasDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.LorasRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class LorasRepositoryImpl( + private val rdsA1111: LorasDataSource.Remote.Automatic1111, + private val rdsSwarm: LorasDataSource.Remote.SwarmUi, + private val swarmSession: SwarmUiSessionDataSource, + private val lds: LorasDataSource.Local, + private val preferenceManager: PreferenceManager, +) : LorasRepository { + + override fun fetchLoras(): Completable = when (preferenceManager.source) { + ServerSource.AUTOMATIC1111 -> rdsA1111 + .fetchLoras() + .flatMapCompletable(lds::insertLoras) + + ServerSource.SWARM_UI -> swarmSession + .getSessionId() + .flatMap(rdsSwarm::fetchLoras) + .let(swarmSession::handleSessionError) + .flatMapCompletable(lds::insertLoras) + + else -> Completable.complete() + } + + override fun fetchAndGetLoras(): Single> = fetchLoras() + .onErrorComplete() + .andThen(getLoras()) + + override fun getLoras(): Single> = lds.getLoras() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/MediaPipeGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/MediaPipeGenerationRepositoryImpl.kt new file mode 100644 index 000000000..48ac99bc1 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/MediaPipeGenerationRepositoryImpl.kt @@ -0,0 +1,51 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.data.mappers.mapLocalDiffusionToAiGenResult +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.mediapipe.MediaPipe +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.MediaPipeGenerationRepository +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers + +internal class MediaPipeGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + backgroundWorkObserver: BackgroundWorkObserver, + mediaFileManager: MediaFileManager, + preferenceManager: PreferenceManager, + blurHashEncoder: BlurHashEncoder, + private val schedulersProvider: SchedulersProvider, + private val mediaPipe: MediaPipe, + private val bitmapToBase64Converter: BitmapToBase64Converter, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), MediaPipeGenerationRepository { + + override fun generateFromText(payload: TextToImagePayload): Single = mediaPipe + .process(payload) + .subscribeOn(schedulersProvider.singleThread.let(Schedulers::from)) + .map(BitmapToBase64Converter::Input) + .flatMap(bitmapToBase64Converter::invoke) + .map(BitmapToBase64Converter.Output::base64ImageString) + .map { base64 -> payload to base64 } + .map(Pair::mapLocalDiffusionToAiGenResult) + .flatMap(::insertGenerationResult) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/OpenAiGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/OpenAiGenerationRepositoryImpl.kt new file mode 100644 index 000000000..7c948988a --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/OpenAiGenerationRepositoryImpl.kt @@ -0,0 +1,39 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.OpenAiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.OpenAiGenerationRepository + +internal class OpenAiGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + preferenceManager: PreferenceManager, + backgroundWorkObserver: BackgroundWorkObserver, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val remoteDataSource: OpenAiGenerationDataSource.Remote, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), OpenAiGenerationRepository { + + override fun validateApiKey() = remoteDataSource.validateApiKey() + + override fun generateFromText(payload: TextToImagePayload) = remoteDataSource + .textToImage(payload) + .flatMap(::insertGenerationResult) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/QnnGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/QnnGenerationRepositoryImpl.kt new file mode 100644 index 000000000..05d3758db --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/QnnGenerationRepositoryImpl.kt @@ -0,0 +1,81 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.data.mappers.QnnGenerationData +import dev.minios.pdaiv1.data.mappers.mapQnnResultToAiGenResult +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.qnn.LocalQnn +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import io.reactivex.rxjava3.core.Completable + +internal class QnnGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + backgroundWorkObserver: BackgroundWorkObserver, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val preferenceManager: PreferenceManager, + private val localQnn: LocalQnn, + private val bitmapToBase64Converter: BitmapToBase64Converter, + private val schedulersProvider: SchedulersProvider, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), QnnGenerationRepository { + + override fun observeStatus() = localQnn.observeStatus() + + override fun generateFromText(payload: TextToImagePayload) = localQnn + .processTextToImage(payload) + .subscribeOn(schedulersProvider.io) + .flatMap { qnnResult -> + bitmapToBase64Converter(BitmapToBase64Converter.Input(qnnResult.bitmap)) + .map { output -> + QnnGenerationData( + payload = payload, + base64 = output.base64ImageString, + seed = qnnResult.seed, + width = qnnResult.width, + height = qnnResult.height, + ) + } + } + .map(QnnGenerationData::mapQnnResultToAiGenResult) + .flatMap(::insertGenerationResult) + + override fun generateFromImage(payload: ImageToImagePayload) = localQnn + .processImageToImage(payload) + .subscribeOn(schedulersProvider.io) + .flatMap { qnnResult -> + bitmapToBase64Converter(BitmapToBase64Converter.Input(qnnResult.bitmap)) + .map { output -> + QnnGenerationData( + payload = payload, + base64 = output.base64ImageString, + seed = qnnResult.seed, + width = qnnResult.width, + height = qnnResult.height, + ) + } + } + .map(QnnGenerationData::mapQnnResultToAiGenResult) + .flatMap(::insertGenerationResult) + + override fun interruptGeneration(): Completable = localQnn.interrupt() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/RandomImageRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/RandomImageRepositoryImpl.kt new file mode 100644 index 000000000..760fb220e --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/RandomImageRepositoryImpl.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.RandomImageDataSource +import dev.minios.pdaiv1.domain.repository.RandomImageRepository + +internal class RandomImageRepositoryImpl( + private val remoteDataSource: RandomImageDataSource.Remote, +) : RandomImageRepository { + + override fun fetchAndGet() = remoteDataSource.fetch() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/ReportRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/ReportRepositoryImpl.kt new file mode 100644 index 000000000..9a5fd6385 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/ReportRepositoryImpl.kt @@ -0,0 +1,26 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.ReportDataSource +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.ReportRepository +import io.reactivex.rxjava3.core.Completable + +internal class ReportRepositoryImpl( + private val rds: ReportDataSource.Remote, + private val preferenceManager: PreferenceManager, +) : ReportRepository { + + override fun send(text: String, reason: ReportReason, image: String): Completable { + val source = preferenceManager.source + val model = when (source) { + ServerSource.HUGGING_FACE -> preferenceManager.huggingFaceModel + ServerSource.STABILITY_AI -> preferenceManager.stabilityAiEngineId + ServerSource.LOCAL_MICROSOFT_ONNX -> preferenceManager.localOnnxModelId + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> preferenceManager.localMediaPipeModelId + else -> "" + } + return rds.send(text, reason, image, source.toString(), model) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/ServerConfigurationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/ServerConfigurationRepositoryImpl.kt new file mode 100755 index 000000000..f77763413 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/ServerConfigurationRepositoryImpl.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.ServerConfigurationDataSource +import dev.minios.pdaiv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository + +internal class ServerConfigurationRepositoryImpl( + private val remoteDataSource: ServerConfigurationDataSource.Remote, + private val localDataSource: ServerConfigurationDataSource.Local, +) : ServerConfigurationRepository { + + override fun fetchConfiguration() = remoteDataSource + .fetchConfiguration() + .flatMapCompletable(localDataSource::save) + + override fun fetchAndGetConfiguration() = fetchConfiguration() + .onErrorComplete() + .andThen(getConfiguration()) + + override fun getConfiguration() = localDataSource.get() + + override fun updateConfiguration(configuration: ServerConfiguration) = remoteDataSource + .updateConfiguration(configuration) +} diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiCreditsRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiCreditsRepositoryImpl.kt similarity index 84% rename from data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiCreditsRepositoryImpl.kt rename to data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiCreditsRepositoryImpl.kt index ed9ef0d6b..2e70052db 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/StabilityAiCreditsRepositoryImpl.kt +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiCreditsRepositoryImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.StabilityAiCreditsRepository +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.StabilityAiCreditsRepository import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiEnginesRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiEnginesRepositoryImpl.kt new file mode 100644 index 000000000..feeda7a52 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiEnginesRepositoryImpl.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.StabilityAiEnginesDataSource +import dev.minios.pdaiv1.domain.repository.StabilityAiEnginesRepository + +internal class StabilityAiEnginesRepositoryImpl( + private val remoteDataSource: StabilityAiEnginesDataSource.Remote, +) : StabilityAiEnginesRepository { + + override fun fetchAndGet() = remoteDataSource.fetch() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiGenerationRepositoryImpl.kt new file mode 100644 index 000000000..ef2a9b7c5 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/StabilityAiGenerationRepositoryImpl.kt @@ -0,0 +1,75 @@ +package dev.minios.pdaiv1.data.repository + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository +import io.reactivex.rxjava3.core.Single +import java.io.ByteArrayOutputStream + +internal class StabilityAiGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + backgroundWorkObserver: BackgroundWorkObserver, + private val base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val preferenceManager: PreferenceManager, + private val generationRds: StabilityAiGenerationDataSource.Remote, + private val creditsRds: StabilityAiCreditsDataSource.Remote, + private val creditsLds: StabilityAiCreditsDataSource.Local, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), StabilityAiGenerationRepository { + + override fun validateApiKey() = generationRds.validateApiKey() + + override fun generateFromText(payload: TextToImagePayload) = generationRds + .textToImage(preferenceManager.stabilityAiEngineId, payload) + .flatMap(::insertGenerationResult) + .flatMap(::refreshCredits) + + override fun generateFromImage(payload: ImageToImagePayload) = payload + .base64Image + .let(Base64ToBitmapConverter::Input) + .let(base64ToBitmapConverter::invoke) + .map(Base64ToBitmapConverter.Output::bitmap) + .map { bmp -> + val stream = ByteArrayOutputStream() + bmp.compress(Bitmap.CompressFormat.PNG, 100, stream) + stream.toByteArray() + } + .flatMap { bytes -> + generationRds.imageToImage( + engineId = preferenceManager.stabilityAiEngineId, + payload = payload, + imageBytes = bytes, + ) + } + .flatMap(::insertGenerationResult) + .flatMap(::refreshCredits) + + + private fun refreshCredits(ai: AiGenerationResult) = creditsRds + .fetch() + .flatMapCompletable(creditsLds::save) + .onErrorComplete() + .andThen(Single.just(ai)) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionGenerationRepositoryImpl.kt new file mode 100755 index 000000000..cd61b1327 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionGenerationRepositoryImpl.kt @@ -0,0 +1,62 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionGenerationDataSource +import dev.minios.pdaiv1.domain.demo.ImageToImageDemo +import dev.minios.pdaiv1.domain.demo.TextToImageDemo +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import io.reactivex.rxjava3.core.Single + +internal class StableDiffusionGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + backgroundWorkObserver: BackgroundWorkObserver, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val remoteDataSource: StableDiffusionGenerationDataSource.Remote, + private val preferenceManager: PreferenceManager, + private val textToImageDemo: TextToImageDemo, + private val imageToImageDemo: ImageToImageDemo, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), StableDiffusionGenerationRepository { + + override fun checkApiAvailability() = remoteDataSource.checkAvailability() + + override fun checkApiAvailability(url: String) = remoteDataSource.checkAvailability(url) + + override fun generateFromText(payload: TextToImagePayload): Single { + val chain = + if (preferenceManager.demoMode) textToImageDemo.getDemoBase64(payload) + else remoteDataSource.textToImage(payload) + + return chain.flatMap(::insertGenerationResult) + } + + override fun generateFromImage(payload: ImageToImagePayload): Single { + val chain = + if (preferenceManager.demoMode) imageToImageDemo.getDemoBase64(payload) + else remoteDataSource.imageToImage(payload) + + return chain.flatMap(::insertGenerationResult) + } + + override fun interruptGeneration() = remoteDataSource.interruptGeneration() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionHyperNetworksRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionHyperNetworksRepositoryImpl.kt new file mode 100644 index 000000000..c67649ccb --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionHyperNetworksRepositoryImpl.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.StableDiffusionHyperNetworksDataSource +import dev.minios.pdaiv1.domain.repository.StableDiffusionHyperNetworksRepository + +internal class StableDiffusionHyperNetworksRepositoryImpl( + private val remoteDataSource: StableDiffusionHyperNetworksDataSource.Remote, + private val localDataSource: StableDiffusionHyperNetworksDataSource.Local, +) : StableDiffusionHyperNetworksRepository { + + override fun fetchHyperNetworks() = remoteDataSource + .fetchHyperNetworks() + .flatMapCompletable(localDataSource::insertHyperNetworks) + + override fun fetchAndGetHyperNetworks() = fetchHyperNetworks() + .onErrorComplete() + .andThen(getHyperNetworks()) + + override fun getHyperNetworks() = localDataSource.getHyperNetworks() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionModelsRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionModelsRepositoryImpl.kt new file mode 100755 index 000000000..fe127791b --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionModelsRepositoryImpl.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.StableDiffusionModelsDataSource +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel +import dev.minios.pdaiv1.domain.repository.StableDiffusionModelsRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class StableDiffusionModelsRepositoryImpl( + private val remoteDataSource: StableDiffusionModelsDataSource.Remote, + private val localDataSource: StableDiffusionModelsDataSource.Local, +) : StableDiffusionModelsRepository { + + override fun fetchModels(): Completable = remoteDataSource + .fetchSdModels() + .flatMapCompletable(localDataSource::insertModels) + + override fun fetchAndGetModels(): Single> = fetchModels() + .onErrorComplete() + .andThen(getModels()) + + override fun getModels(): Single> = + localDataSource.getModels() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionSamplersRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionSamplersRepositoryImpl.kt new file mode 100755 index 000000000..eac416392 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/StableDiffusionSamplersRepositoryImpl.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.StableDiffusionSamplersDataSource +import dev.minios.pdaiv1.domain.repository.StableDiffusionSamplersRepository + +internal class StableDiffusionSamplersRepositoryImpl( + private val remoteDataSource: StableDiffusionSamplersDataSource.Remote, + private val localDataSource: StableDiffusionSamplersDataSource.Local, +) : StableDiffusionSamplersRepository { + + override fun fetchSamplers() = remoteDataSource + .fetchSamplers() + .flatMapCompletable(localDataSource::insertSamplers) + + override fun getSamplers() = localDataSource.getSamplers() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/SupportersRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/SupportersRepositoryImpl.kt new file mode 100644 index 000000000..704bbe6e2 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/SupportersRepositoryImpl.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.SupportersDataSource +import dev.minios.pdaiv1.domain.entity.Supporter +import dev.minios.pdaiv1.domain.repository.SupportersRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class SupportersRepositoryImpl( + private val rds: SupportersDataSource.Remote, + private val lds: SupportersDataSource.Local, +) : SupportersRepository { + + override fun fetchSupporters(): Completable = rds + .fetch() + .flatMapCompletable(lds::save) + + override fun fetchAndGetSupporters(): Single> = fetchSupporters() + .onErrorComplete() + .andThen(getSupporters()) + + override fun getSupporters(): Single> = lds.getAll() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/SwarmUiGenerationRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/SwarmUiGenerationRepositoryImpl.kt new file mode 100644 index 000000000..d66aba706 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/SwarmUiGenerationRepositoryImpl.kt @@ -0,0 +1,71 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.core.CoreGenerationRepository +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class SwarmUiGenerationRepositoryImpl( + mediaStoreGateway: MediaStoreGateway, + base64ToBitmapConverter: Base64ToBitmapConverter, + localDataSource: GenerationResultDataSource.Local, + backgroundWorkObserver: BackgroundWorkObserver, + mediaFileManager: MediaFileManager, + blurHashEncoder: BlurHashEncoder, + private val preferenceManager: PreferenceManager, + private val session: SwarmUiSessionDataSource, + private val remoteDataSource: SwarmUiGenerationDataSource.Remote, +) : CoreGenerationRepository( + mediaStoreGateway = mediaStoreGateway, + base64ToBitmapConverter = base64ToBitmapConverter, + localDataSource = localDataSource, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + mediaFileManager = mediaFileManager, + blurHashEncoder = blurHashEncoder, +), SwarmUiGenerationRepository { + + override fun checkApiAvailability(): Completable = session + .getSessionId() + .ignoreElement() + + override fun checkApiAvailability(url: String): Completable = session + .getSessionId(url) + .ignoreElement() + + override fun generateFromText(payload: TextToImagePayload): Single = session + .getSessionId() + .flatMap { sessionId -> + remoteDataSource.textToImage( + sessionId = sessionId, + model = preferenceManager.swarmUiModel, + payload = payload, + ) + } + .let(session::handleSessionError) + .flatMap(::insertGenerationResult) + + override fun generateFromImage(payload: ImageToImagePayload): Single = session + .getSessionId() + .flatMap { sessionId -> + remoteDataSource.imageToImage( + sessionId = sessionId, + model = preferenceManager.swarmUiModel, + payload = payload, + ) + } + .let(session::handleSessionError) + .flatMap(::insertGenerationResult) +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/SwarmUiModelsRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/SwarmUiModelsRepositoryImpl.kt new file mode 100644 index 000000000..1d6822cd5 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/SwarmUiModelsRepositoryImpl.kt @@ -0,0 +1,27 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.SwarmUiModelsDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import dev.minios.pdaiv1.domain.repository.SwarmUiModelsRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class SwarmUiModelsRepositoryImpl( + private val session: SwarmUiSessionDataSource, + private val rds: SwarmUiModelsDataSource.Remote, + private val lds: SwarmUiModelsDataSource.Local, +) : SwarmUiModelsRepository { + + override fun fetchModels(): Completable = session + .getSessionId() + .flatMap(rds::fetchSwarmModels) + .let(session::handleSessionError) + .flatMapCompletable(lds::insertModels) + + override fun fetchAndGetModels(): Single> = fetchModels() + .onErrorComplete() + .andThen(getModels()) + + override fun getModels(): Single> = lds.getModels() +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/TemporaryGenerationResultRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/TemporaryGenerationResultRepositoryImpl.kt new file mode 100644 index 000000000..e993f39f0 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/TemporaryGenerationResultRepositoryImpl.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.repository.TemporaryGenerationResultRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +internal class TemporaryGenerationResultRepositoryImpl : TemporaryGenerationResultRepository { + + private var lastCachedResult: AiGenerationResult? = null + + override fun put(result: AiGenerationResult) = Completable.fromAction { + lastCachedResult = result + } + + override fun get(): Single { + return lastCachedResult + ?.let { Single.just(it) } + ?: Single.error(IllegalStateException("No last cached result.")) + } +} diff --git a/data/src/main/java/dev/minios/pdaiv1/data/repository/WakeLockRepositoryImpl.kt b/data/src/main/java/dev/minios/pdaiv1/data/repository/WakeLockRepositoryImpl.kt new file mode 100644 index 000000000..192c44369 --- /dev/null +++ b/data/src/main/java/dev/minios/pdaiv1/data/repository/WakeLockRepositoryImpl.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.data.repository + +import android.os.PowerManager +import dev.minios.pdaiv1.domain.repository.WakeLockRepository + +internal class WakeLockRepositoryImpl( + val powerManager: () -> PowerManager, +) : WakeLockRepository { + + private var _wakeLock: PowerManager.WakeLock? = null + override val wakeLock: PowerManager.WakeLock + get() = _wakeLock ?: run { + val wl = powerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG) + _wakeLock = wl + wl + } + + companion object { + private const val TAG = "PDAI:WakeLock" + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayFactoryTest.kt b/data/src/test/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayFactoryTest.kt deleted file mode 100644 index a4cbefe4f..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/gateway/mediastore/MediaStoreGatewayFactoryTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.shifthackz.aisdv1.data.gateway.mediastore - -import android.content.Context -import android.os.Build -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import io.mockk.mockk -import org.junit.Assert -import org.junit.Test -import java.lang.reflect.Field -import java.lang.reflect.Method -import java.lang.reflect.Modifier - -class MediaStoreGatewayFactoryTest { - - private val stubContext = mockk() - private val stubFileProviderDescriptor = mockk() - - private val factory = MediaStoreGatewayFactory( - context = stubContext, - fileProviderDescriptor = stubFileProviderDescriptor, - ) - - @Test - fun `given app running on Android SDK 26 (O), expected factory returned instance of type MediaStoreGatewayOldImpl`() { - mockSdkInt(Build.VERSION_CODES.O) - val actual = factory.invoke() - Assert.assertEquals(true, actual is MediaStoreGatewayOldImpl) - } - - @Test - fun `given app running on Android SDK 31 (S), expected factory returned instance of type MediaStoreGatewayOldImpl`() { - mockSdkInt(Build.VERSION_CODES.S) - val actual = factory.invoke() - Assert.assertEquals(true, actual is MediaStoreGatewayOldImpl) - } - - @Test - fun `given app running on Android SDK 32 (S_V2), expected factory returned instance of type MediaStoreGatewayOldImpl`() { - mockSdkInt(Build.VERSION_CODES.S_V2) - val actual = factory.invoke() - Assert.assertEquals(true, actual is MediaStoreGatewayImpl) - } - - @Test - fun `given app running on Android SDK 34 (UPSIDE_DOWN_CAKE), expected factory returned instance of type MediaStoreGatewayOldImpl`() { - mockSdkInt(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - val actual = factory.invoke() - Assert.assertEquals(true, actual is MediaStoreGatewayImpl) - } - - private fun mockSdkInt(sdkInt: Int) { - val sdkIntField = Build.VERSION::class.java.getField("SDK_INT") - sdkIntField.isAccessible = true - getModifiersField().also { - it.isAccessible = true - it.set(sdkIntField, sdkIntField.modifiers and Modifier.FINAL.inv()) - } - sdkIntField.set(null, sdkInt) - } - - private fun getModifiersField(): Field { - return try { - Field::class.java.getDeclaredField("modifiers") - } catch (e: NoSuchFieldException) { - try { - val getDeclaredFields0: Method = - Class::class.java.getDeclaredMethod("getDeclaredFields0", Boolean::class.javaPrimitiveType) - getDeclaredFields0.isAccessible = true - val fields = getDeclaredFields0.invoke(Field::class.java, false) as Array - for (field in fields) { - if ("modifiers" == field.name) { - return field - } - } - } catch (ex: ReflectiveOperationException) { - e.addSuppressed(ex) - } - throw e - } - } -} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/AiGenerationResultMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/AiGenerationResultMocks.kt deleted file mode 100644 index f36301b7e..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/AiGenerationResultMocks.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import java.util.Date - -val mockAiGenerationResult = AiGenerationResult( - id = 5598L, - image = "img", - inputImage = "inp", - createdAt = Date(0), - type = AiGenerationResult.Type.IMAGE_TO_IMAGE, - prompt = "prompt", - negativePrompt = "negative", - width = 512, - height = 512, - samplingSteps = 7, - cfgScale = 0.7f, - restoreFaces = true, - sampler = "sampler", - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - denoisingStrength = 1504f, - hidden = false, -) - -val mockAiGenerationResults = listOf(mockAiGenerationResult) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/DownloadableModelResponseMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/DownloadableModelResponseMocks.kt deleted file mode 100644 index deb6dd4d6..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/DownloadableModelResponseMocks.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.response.DownloadableModelResponse - -val mockDownloadableModelsResponse = listOf( - DownloadableModelResponse( - id = "1", - name = "Model 1", - size = "5 Gb", - sources = listOf("https://example.com/1.html"), - ) -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/GenerationResultEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/GenerationResultEntityMocks.kt deleted file mode 100644 index fd8fa9e4d..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/GenerationResultEntityMocks.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.storage.db.persistent.entity.GenerationResultEntity -import java.util.Date - -val mockGenerationResultEntity = GenerationResultEntity( - id = 5598L, - imageBase64 = "img", - originalImageBase64 = "inp", - createdAt = Date(0), - generationType = AiGenerationResult.Type.IMAGE_TO_IMAGE.key, - prompt = "prompt", - negativePrompt = "negative", - width = 512, - height = 512, - samplingSteps = 7, - cfgScale = 0.7f, - restoreFaces = true, - sampler = "sampler", - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - denoisingStrength = 1504f, - hidden = false, -) - -val mockGenerationResultEntities = listOf(mockGenerationResultEntity) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelEntityMocks.kt deleted file mode 100644 index 259228974..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelEntityMocks.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.persistent.entity.HuggingFaceModelEntity - -val mockHuggingFaceModelEntity = HuggingFaceModelEntity( - id = "050598", - name = "Super model", - alias = "❤", - source = "https://life.archive.org/models/unique/050598", -) - -val mockHuggingFaceModelEntities = listOf( - mockHuggingFaceModelEntity, -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelMocks.kt deleted file mode 100644 index 21ad6d07f..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelMocks.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel - -val mockHuggingFaceModel = HuggingFaceModel( - id = "050598", - name = "Super model", - alias = "❤", - source = "https://life.archive.org/models/unique/050598", -) - -val mockHuggingFaceModels = listOf(mockHuggingFaceModel) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ImageToImagePayloadMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ImageToImagePayloadMocks.kt deleted file mode 100644 index 3138ce979..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ImageToImagePayloadMocks.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload - -val mockImageToImagePayload = ImageToImagePayload( - base64Image = "", - base64MaskImage = "", - denoisingStrength = 7f, - prompt = "prompt", - negativePrompt = "negative", - samplingSteps = 12, - cfgScale = 0.7f, - width = 512, - height = 512, - restoreFaces = true, - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - sampler = "sampler", - nsfw = true, - batchCount = 1, - inPaintingMaskInvert = 0, - inPaintFullResPadding = 0, - inPaintingFill = 0, - inPaintFullRes = false, - maskBlur = 0, - stabilityAiClipGuidance = null, - stabilityAiStylePreset = null, -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/LocalAiModelMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/LocalAiModelMocks.kt deleted file mode 100644 index 9aaa20a8b..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/LocalAiModelMocks.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel - -val mockLocalAiModel = LocalAiModel( - id = "5598", - type = LocalAiModel.Type.ONNX, - name = "Model 5598", - size = "5 Gb", - sources = listOf("https://example.com/1.html"), - downloaded = false, - selected = false, -) - -val mockLocalAiModels = listOf(mockLocalAiModel) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/OpenAiResponseMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/OpenAiResponseMocks.kt deleted file mode 100644 index adb54d034..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/OpenAiResponseMocks.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.OpenAiImageRaw -import com.shifthackz.aisdv1.network.response.OpenAiResponse - -val mockSuccessOpenAiResponse = OpenAiResponse( - created = System.currentTimeMillis(), - data = listOf( - OpenAiImageRaw( - "base64", - "https://openai.com", - "prompt", - ), - ), -) - -val mockBadOpenAiResponse = OpenAiResponse( - created = System.currentTimeMillis(), - data = emptyList(), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SdEmbeddingsResponseMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SdEmbeddingsResponseMocks.kt deleted file mode 100644 index d2250d174..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SdEmbeddingsResponseMocks.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.response.SdEmbeddingsResponse - -val mockSdEmbeddingsResponse = SdEmbeddingsResponse( - loaded = mapOf("1504" to "5598"), -) - -val mockEmptySdEmbeddingsResponse = SdEmbeddingsResponse( - loaded = emptyMap(), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SdGenerationResponseMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SdGenerationResponseMocks.kt deleted file mode 100644 index fa6aec9ca..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SdGenerationResponseMocks.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.response.SdGenerationResponse - -val mockSdGenerationResponse = SdGenerationResponse( - images = listOf("base64"), - info = "info", -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationEntityMocks.kt deleted file mode 100644 index a649dc5bc..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationEntityMocks.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.cache.entity.ServerConfigurationEntity - -val mockServerConfigurationEntity = ServerConfigurationEntity( - serverId = "5598", - sdModelCheckpoint = "checkpoint", -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationMocks.kt deleted file mode 100644 index f6a0fb6fc..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationMocks.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration - -val mockServerConfiguration = ServerConfiguration("checkpoint") diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationRawMocks.kt deleted file mode 100644 index 885f7969d..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/ServerConfigurationRawMocks.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.ServerConfigurationRaw - -val mockServerConfigurationRaw = ServerConfigurationRaw("5598") diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityAiEngineMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityAiEngineMocks.kt deleted file mode 100644 index 1616763ff..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityAiEngineMocks.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine - -val mockStabilityAiEngines = listOf( - StabilityAiEngine( - id = "5598", - name = "engine_5598", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityAiEngineRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityAiEngineRawMocks.kt deleted file mode 100644 index 9dcd03055..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityAiEngineRawMocks.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.StabilityAiEngineRaw - -val mockStabilityAiEnginesRaw = listOf( - StabilityAiEngineRaw( - description = "❤", - id = "5598", - name = "Super engine", - type = "unique", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionEmbeddingEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionEmbeddingEntityMocks.kt deleted file mode 100644 index 904054894..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionEmbeddingEntityMocks.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity - -val mockStableDiffusionEmbeddingEntities = listOf( - StableDiffusionEmbeddingEntity( - id = "5598", - keyword = "keyword_5598", - ), - StableDiffusionEmbeddingEntity( - id = "151297", - keyword = "keyword_151297", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionEmbeddingMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionEmbeddingMocks.kt deleted file mode 100644 index c4a5e9d27..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionEmbeddingMocks.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.Embedding - -val mockEmbeddings = listOf( - Embedding("keyword_5598"), - Embedding("keyword_151297"), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkEntityMocks.kt deleted file mode 100644 index 9d6181113..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkEntityMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity - -val mockStableDiffusionHyperNetworkEntities = listOf( - StableDiffusionHyperNetworkEntity( - id = "5598", - name = "net_5598", - path = "/unknown", - ), - StableDiffusionHyperNetworkEntity( - id = "151297", - name = "net_151297", - path = "/unknown", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkMocks.kt deleted file mode 100644 index 6e4c5931e..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkMocks.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork - -val mockStableDiffusionHyperNetworks = listOf( - StableDiffusionHyperNetwork( - name = "net_5598", - path = "/unknown", - ), - StableDiffusionHyperNetwork( - name = "net_151297", - path = "/unknown", - ) -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkRawMocks.kt deleted file mode 100644 index d3de9fb20..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionHyperNetworkRawMocks.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.StableDiffusionHyperNetworkRaw - -val mockStableDiffusionHyperNetworkRaw = listOf( - StableDiffusionHyperNetworkRaw( - name = "5598", - path = "Unknown", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraEntityMocks.kt deleted file mode 100644 index d0ed99856..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraEntityMocks.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionLoraEntity - -val mockStableDiffusionLoraEntities = listOf( - StableDiffusionLoraEntity( - id = "5598", - name = "name_5598", - alias = "alias_5598", - path = "/unknown", - ), - StableDiffusionLoraEntity( - id = "151297", - name = "name_151297", - alias = "alias_151297", - path = "/unknown", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraMocks.kt deleted file mode 100644 index a6531aa22..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.LoRA - -val mockStableDiffusionLoras = listOf( - LoRA( - name = "name_5598", - alias = "alias_5598", - path = "/unknown", - ), - LoRA( - name = "name_151297", - alias = "alias_151297", - path = "/unknown", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraRawMocks.kt deleted file mode 100644 index 0f2e8bea1..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionLoraRawMocks.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.StableDiffusionLoraRaw - -val mockStableDiffusionLoraRaw = listOf( - StableDiffusionLoraRaw( - name = "Super lora", - alias = "5598", - path = "Unknown", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelMocks.kt deleted file mode 100644 index 25222e867..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelMocks.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel - -val mockStableDiffusionModels = listOf( - StableDiffusionModel( - title = "title_5598", - modelName = "name_5598", - hash = "hash_5598", - sha256 = "sha_5598", - filename = "file_5598", - config = "config_5598", - ), - StableDiffusionModel( - title = "title_151297", - modelName = "name_151297", - hash = "hash_151297", - sha256 = "sha_151297", - filename = "file_151297", - config = "config_151297", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelRawMocks.kt deleted file mode 100644 index c93328862..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelRawMocks.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.StableDiffusionModelRaw - -val mockStableDiffusionModelRaw = listOf( - StableDiffusionModelRaw( - title = "5598", - modelName = "5598", - hash = "hash5598", - sha256 = "sha5598", - filename = "Unknown", - config = "Unconfigurable", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerEntityMocks.kt deleted file mode 100644 index 860e72838..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerEntityMocks.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionSamplerEntity - -val mockStableDiffusionSamplerEntities = listOf( - StableDiffusionSamplerEntity( - id = "5598", - name = "name_5598", - aliases = emptyList(), - options = emptyMap(), - ), - StableDiffusionSamplerEntity( - id = "151297", - name = "name_151297", - aliases = emptyList(), - options = emptyMap(), - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerMocks.kt deleted file mode 100644 index f8640a7ac..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler - -val mockStableDiffusionSamplers = listOf( - StableDiffusionSampler( - name = "name_5598", - aliases = emptyList(), - options = emptyMap(), - ), - StableDiffusionSampler( - name = "name_151297", - aliases = emptyList(), - options = emptyMap(), - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerRawMocks.kt deleted file mode 100644 index f92d5abcd..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionSamplerRawMocks.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.StableDiffusionSamplerRaw - -val mockStableDiffusionSamplerRaw = listOf( - StableDiffusionSamplerRaw( - "5598", - listOf(), - mapOf(), - ) -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterEntityMocks.kt deleted file mode 100644 index 2db1be7cf..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterEntityMocks.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.persistent.entity.SupporterEntity -import java.util.Date - -val mockSupporterEntities = listOf( - SupporterEntity( - id = 5598, - name = "NZ", - date = Date(1998, 5, 5), - message = "I always wanted support you ❤", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterMocks.kt deleted file mode 100644 index a82ddc15e..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterMocks.kt +++ /dev/null @@ -1,15 +0,0 @@ -@file:Suppress("DEPRECATION") - -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.Supporter -import java.util.Date - -val mockSupporters = listOf( - Supporter( - id = 5598, - name = "NZ", - date = Date(1998, 5, 5), - message = "I always wanted support you ❤", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterRawMocks.kt deleted file mode 100644 index e65e8624e..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SupporterRawMocks.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.SupporterRaw - -val mockSupportersRaw = listOf( - SupporterRaw( - id = 5598, - name = "NZ", - date = "1998-05-05", - message = "I always wanted support you ❤", - type = "bmc", - amount = "55.98", - currency = "USD", - ), -) \ No newline at end of file diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiGenerationResponseMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiGenerationResponseMocks.kt deleted file mode 100644 index e92ab5618..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiGenerationResponseMocks.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.response.SwarmUiGenerationResponse - -val mockSwarmUiGenerationResponse = SwarmUiGenerationResponse( - images = listOf("/tmp/img.jpg"), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelEntityMocks.kt deleted file mode 100644 index 1f1513170..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelEntityMocks.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.storage.db.cache.entity.SwarmUiModelEntity - -val mockSwarmUiModelEntities = listOf( - SwarmUiModelEntity( - id = "5598", - name = "5598", - title = "5598", - author = "", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelMocks.kt deleted file mode 100644 index 2917e3bd7..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelMocks.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel - -val mockSwarmUiModels = listOf( - SwarmUiModel( - name = "5598", - title = "5598", - author = "5598", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelRawMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelRawMocks.kt deleted file mode 100644 index 19c9218a8..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/SwarmUiModelRawMocks.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.network.model.SwarmUiModelRaw - -val mockSwarmUiModelsRaw = listOf( - SwarmUiModelRaw( - name = "5598", - title = "5598", - author = "5598", - ), -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/TextToImagePayloadMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/TextToImagePayloadMocks.kt deleted file mode 100644 index ea38da27c..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/TextToImagePayloadMocks.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.data.mocks - -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload - -val mockTextToImagePayload = TextToImagePayload( - prompt = "prompt", - negativePrompt = "negative", - samplingSteps = 12, - cfgScale = 0.7f, - width = 512, - height = 512, - restoreFaces = true, - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - sampler = "sampler", - nsfw = true, - batchCount = 1, - quality = null, - style = null, - openAiModel = null, - stabilityAiClipGuidance = null, - stabilityAiStylePreset = null, -) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImplTest.kt b/data/src/test/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImplTest.kt deleted file mode 100644 index a95b5d494..000000000 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImplTest.kt +++ /dev/null @@ -1,338 +0,0 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResults -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import io.mockk.every -import io.mockk.mockk -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single -import org.junit.Test - -class GenerationResultRepositoryImplTest { - - private val stubException = Throwable("Something went wrong.") - private val stubPreferenceManager = mockk() - private val stubMediaStoreGateway = mockk() - private val stubBase64ToBitmapConverter = mockk() - private val stubLocalDataSource = mockk() - - private val repository = GenerationResultRepositoryImpl( - preferenceManager = stubPreferenceManager, - mediaStoreGateway = stubMediaStoreGateway, - base64ToBitmapConverter = stubBase64ToBitmapConverter, - localDataSource = stubLocalDataSource, - ) - - @Test - fun `given attempt to get all, local returns data, expected valid domain model list value`() { - every { - stubLocalDataSource.queryAll() - } returns Single.just(mockAiGenerationResults) - - repository - .getAll() - .test() - .assertNoErrors() - .assertValue(mockAiGenerationResults) - .await() - .assertComplete() - } - - @Test - fun `given attempt to get all, local returns empty data, expected empty domain model list value`() { - every { - stubLocalDataSource.queryAll() - } returns Single.just(emptyList()) - - repository - .getAll() - .test() - .assertNoErrors() - .assertValue(emptyList()) - .await() - .assertComplete() - } - - @Test - fun `given attempt to get all, local throws exception, expected error value`() { - every { - stubLocalDataSource.queryAll() - } returns Single.error(stubException) - - repository - .getAll() - .test() - .assertError(stubException) - .assertNoValues() - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to get page, local returns data, expected valid domain model list value`() { - every { - stubLocalDataSource.queryPage(any(), any()) - } returns Single.just(mockAiGenerationResults) - - repository - .getPage(20, 0) - .test() - .assertNoErrors() - .assertValue(mockAiGenerationResults) - .await() - .assertComplete() - } - - @Test - fun `given attempt to get page, local returns empty data, expected empty domain model list value`() { - every { - stubLocalDataSource.queryPage(any(), any()) - } returns Single.just(emptyList()) - - repository - .getPage(20, 0) - .test() - .assertNoErrors() - .assertValue(emptyList()) - .await() - .assertComplete() - } - - @Test - fun `given attempt to get page, local throws exception, expected error value`() { - every { - stubLocalDataSource.queryPage(any(), any()) - } returns Single.error(stubException) - - repository - .getPage(20, 0) - .test() - .assertError(stubException) - .assertNoValues() - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to get media store info, gateway returned data, expected valid media store info value`() { - every { - stubMediaStoreGateway.getInfo() - } returns MediaStoreInfo() - - repository - .getMediaStoreInfo() - .test() - .assertNoErrors() - .assertValue(MediaStoreInfo()) - .await() - .assertComplete() - } - - @Test - fun `given attempt to get media store info, gateway throws exception, expected error value`() { - every { - stubMediaStoreGateway.getInfo() - } throws stubException - - repository - .getMediaStoreInfo() - .test() - .assertError(stubException) - .assertNoValues() - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to get by id, local returns data, expected valid domain model value`() { - every { - stubLocalDataSource.queryById(any()) - } returns Single.just(mockAiGenerationResult) - - repository - .getById(5598L) - .test() - .assertNoErrors() - .assertValue(mockAiGenerationResult) - .await() - .assertComplete() - } - - @Test - fun `given attempt to get by id, local throws exception, expected error value`() { - every { - stubLocalDataSource.queryById(any()) - } returns Single.error(stubException) - - repository - .getById(5598L) - .test() - .assertError(stubException) - .assertNoValues() - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to delete by id list, local delete success, expected complete value`() { - every { - stubLocalDataSource.deleteByIdList(any()) - } returns Completable.complete() - - repository - .deleteByIdList(listOf(5598L, 151297L)) - .test() - .assertNoErrors() - .await() - .assertComplete() - } - - @Test - fun `given attempt to delete by id list, local delete fails, expected error value`() { - every { - stubLocalDataSource.deleteByIdList(any()) - } returns Completable.error(stubException) - - repository - .deleteByIdList(listOf(5598L, 151297L)) - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to delete by id, local delete success, expected complete value`() { - every { - stubLocalDataSource.deleteById(any()) - } returns Completable.complete() - - repository - .deleteById(5598L) - .test() - .assertNoErrors() - .await() - .assertComplete() - } - - @Test - fun `given attempt to delete by id, local delete fails, expected error value`() { - every { - stubLocalDataSource.deleteById(any()) - } returns Completable.error(stubException) - - repository - .deleteById(5598L) - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to delete all, local delete success, expected complete value`() { - every { - stubLocalDataSource.deleteAll() - } returns Completable.complete() - - repository - .deleteAll() - .test() - .assertNoErrors() - .await() - .assertComplete() - } - - @Test - fun `given attempt to delete all, local delete fails, expected complete value`() { - every { - stubLocalDataSource.deleteAll() - } returns Completable.error(stubException) - - repository - .deleteAll() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to insert data, local insert success, expected id of inserted model value`() { - every { - stubPreferenceManager.saveToMediaStore - } returns false - - every { - stubLocalDataSource.insert(any()) - } returns Single.just(mockAiGenerationResult.id) - - repository - .insert(mockAiGenerationResult) - .test() - .assertNoErrors() - .assertValue(5598L) - .await() - .assertComplete() - } - - @Test - fun `given attempt to insert data, local insert fails, expected error value`() { - every { - stubPreferenceManager.saveToMediaStore - } returns false - - every { - stubLocalDataSource.insert(any()) - } returns Single.error(stubException) - - repository - .insert(mockAiGenerationResult) - .test() - .assertError(stubException) - .assertNoValues() - .await() - .assertNotComplete() - } - - @Test - fun `given attempt to toggle image visibility, process succeeds, expected boolean value`() { - every { - stubLocalDataSource.queryById(any()) - } returns Single.just(mockAiGenerationResult) - - every { - stubLocalDataSource.insert(any()) - } returns Single.just(5598L) - - repository - .toggleVisibility(5598L) - .test() - .await() - .assertComplete() - } - - @Test - fun `given attempt to toggle image visibility, error occurs, expected boolean value`() { - every { - stubLocalDataSource.queryById(any()) - } returns Single.just(mockAiGenerationResult) - - every { - stubLocalDataSource.insert(any()) - } returns Single.error(stubException) - - repository - .toggleVisibility(5598L) - .test() - .assertError(stubException) - .assertNoValues() - .await() - .assertNotComplete() - } -} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/gateway/DatabaseClearGatewayImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/gateway/DatabaseClearGatewayImplTest.kt similarity index 91% rename from data/src/test/java/com/shifthackz/aisdv1/data/gateway/DatabaseClearGatewayImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/gateway/DatabaseClearGatewayImplTest.kt index 6bd644832..912591be7 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/gateway/DatabaseClearGatewayImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/gateway/DatabaseClearGatewayImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.gateway +package dev.minios.pdaiv1.data.gateway -import com.shifthackz.aisdv1.storage.gateway.GatewayClearCacheDb -import com.shifthackz.aisdv1.storage.gateway.GatewayClearPersistentDb +import dev.minios.pdaiv1.storage.gateway.GatewayClearCacheDb +import dev.minios.pdaiv1.storage.gateway.GatewayClearPersistentDb import io.mockk.every import io.mockk.mockk import org.junit.Test diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/gateway/ServerConnectivityGatewayImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/gateway/ServerConnectivityGatewayImplTest.kt similarity index 94% rename from data/src/test/java/com/shifthackz/aisdv1/data/gateway/ServerConnectivityGatewayImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/gateway/ServerConnectivityGatewayImplTest.kt index 572b308eb..0e6327c71 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/gateway/ServerConnectivityGatewayImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/gateway/ServerConnectivityGatewayImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.gateway +package dev.minios.pdaiv1.data.gateway -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.network.connectivity.ConnectivityMonitor +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.network.connectivity.ConnectivityMonitor import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Observable diff --git a/data/src/test/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayFactoryTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayFactoryTest.kt new file mode 100644 index 000000000..02298dd82 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/gateway/mediastore/MediaStoreGatewayFactoryTest.kt @@ -0,0 +1,62 @@ +package dev.minios.pdaiv1.data.gateway.mediastore + +import android.content.Context +import dev.minios.pdaiv1.core.common.extensions.shouldUseNewMediaStore +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class MediaStoreGatewayFactoryTest { + + private val stubContext = mockk() + private val stubFileProviderDescriptor = mockk() + + private val factory = MediaStoreGatewayFactory( + context = stubContext, + fileProviderDescriptor = stubFileProviderDescriptor, + ) + + @Before + fun setUp() { + mockkStatic(::shouldUseNewMediaStore) + } + + @After + fun tearDown() { + unmockkStatic(::shouldUseNewMediaStore) + } + + @Test + fun `given app running on Android SDK 26 (O), expected factory returned instance of type MediaStoreGatewayOldImpl`() { + every { shouldUseNewMediaStore() } returns false + val actual = factory.invoke() + Assert.assertEquals(true, actual is MediaStoreGatewayOldImpl) + } + + @Test + fun `given app running on Android SDK 31 (S), expected factory returned instance of type MediaStoreGatewayOldImpl`() { + every { shouldUseNewMediaStore() } returns false + val actual = factory.invoke() + Assert.assertEquals(true, actual is MediaStoreGatewayOldImpl) + } + + @Test + fun `given app running on Android SDK 32 (S_V2), expected factory returned instance of type MediaStoreGatewayImpl`() { + every { shouldUseNewMediaStore() } returns true + val actual = factory.invoke() + Assert.assertEquals(true, actual is MediaStoreGatewayImpl) + } + + @Test + fun `given app running on Android SDK 34 (UPSIDE_DOWN_CAKE), expected factory returned instance of type MediaStoreGatewayImpl`() { + every { shouldUseNewMediaStore() } returns true + val actual = factory.invoke() + Assert.assertEquals(true, actual is MediaStoreGatewayImpl) + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/DownloadableModelLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/DownloadableModelLocalDataSourceTest.kt similarity index 92% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/DownloadableModelLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/DownloadableModelLocalDataSourceTest.kt index 90fe0cbed..ba0809dcc 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/DownloadableModelLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/DownloadableModelLocalDataSourceTest.kt @@ -1,16 +1,16 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.data.mocks.mockLocalAiModels -import com.shifthackz.aisdv1.data.mocks.mockLocalModelEntities -import com.shifthackz.aisdv1.data.mocks.mockLocalModelEntity -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.storage.db.persistent.dao.LocalModelDao -import com.shifthackz.aisdv1.storage.db.persistent.entity.LocalModelEntity +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.data.mocks.mockLocalAiModels +import dev.minios.pdaiv1.data.mocks.mockLocalModelEntities +import dev.minios.pdaiv1.data.mocks.mockLocalModelEntity +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.storage.db.persistent.dao.LocalModelDao +import dev.minios.pdaiv1.storage.db.persistent.entity.LocalModelEntity import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.BackpressureStrategy @@ -111,7 +111,7 @@ class DownloadableModelLocalDataSourceTest { val expected = buildList { addAll(mockLocalModelEntities.mapEntityToDomain()) - add(LocalAiModel.CustomOnnx.copy(downloaded = true)) + add(LocalAiModel.CustomOnnx.copy(downloaded = false)) } localDataSource @@ -143,7 +143,7 @@ class DownloadableModelLocalDataSourceTest { .getAllOnnx() .test() .assertNoErrors() - .assertValue(listOf(LocalAiModel.CustomOnnx.copy(downloaded = true))) + .assertValue(listOf(LocalAiModel.CustomOnnx.copy(downloaded = false))) .await() .assertComplete() } @@ -214,7 +214,7 @@ class DownloadableModelLocalDataSourceTest { } @Test - fun `given attempt to get model by id, dao throws exception, expected error true`() { + fun `given attempt to get model by id, dao throws exception, expected error`() { every { stubDao.queryById(any()) } returns Single.error(stubException) @@ -331,7 +331,7 @@ class DownloadableModelLocalDataSourceTest { stubObserver .assertNoErrors() - .assertValueAt(0, listOf(LocalAiModel.CustomOnnx.copy(downloaded = true))) + .assertValueAt(0, listOf(LocalAiModel.CustomOnnx.copy(downloaded = false))) stubLocalModels.onNext(mockLocalModelEntities) @@ -339,7 +339,7 @@ class DownloadableModelLocalDataSourceTest { .assertNoErrors() .assertValueAt(1, buildList { addAll(mockLocalModelEntities.mapEntityToDomain()) - add(LocalAiModel.CustomOnnx.copy(downloaded = true)) + add(LocalAiModel.CustomOnnx.copy(downloaded = false)) }) } diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/EmbeddingsLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/EmbeddingsLocalDataSourceTest.kt similarity index 92% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/EmbeddingsLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/EmbeddingsLocalDataSourceTest.kt index f5a8c652f..f0d97c8d6 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/EmbeddingsLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/EmbeddingsLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockEmbeddings -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionEmbeddingEntities -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionEmbeddingDao +import dev.minios.pdaiv1.data.mocks.mockEmbeddings +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionEmbeddingEntities +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionEmbeddingDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/dev/minios/pdaiv1/data/local/FalAiEndpointLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/FalAiEndpointLocalDataSourceTest.kt new file mode 100644 index 000000000..e4e7bb6f9 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/FalAiEndpointLocalDataSourceTest.kt @@ -0,0 +1,187 @@ +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mocks.mockFalAiEndpoint +import dev.minios.pdaiv1.data.mocks.mockFalAiEndpoints +import dev.minios.pdaiv1.storage.db.persistent.dao.FalAiEndpointDao +import dev.minios.pdaiv1.storage.db.persistent.entity.FalAiEndpointEntity +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class FalAiEndpointLocalDataSourceTest { + + private val stubException = Throwable("Database error") + private val stubDao = mockk() + + private val localDataSource = FalAiEndpointLocalDataSource(stubDao) + + private val stubEntity = FalAiEndpointEntity( + id = "fal-ai/flux/schnell", + endpointId = "fal-ai/flux/schnell", + title = "FLUX.1 [schnell]", + description = "Fast text to image generation", + category = "TEXT_TO_IMAGE", + group = "FLUX", + thumbnailUrl = "https://fal.ai/thumbnails/flux-schnell.jpg", + playgroundUrl = "https://fal.ai/models/fal-ai/flux/schnell", + documentationUrl = "https://fal.ai/models/fal-ai/flux/schnell/api", + isCustom = true, + schemaJson = """{"baseUrl":"https://queue.fal.run","submissionPath":"/fal-ai/flux/schnell","inputProperties":[{"name":"prompt","title":"Prompt","description":"The prompt","type":"STRING","default":"","minimum":null,"maximum":null,"enumValues":null,"isRequired":true,"isImageInput":false}],"requiredProperties":["prompt"],"propertyOrder":["prompt"]}""", + ) + + @Test + fun `given attempt to observe all, dao returns entities, expected domain models`() { + every { + stubDao.observeAll() + } returns Flowable.just(listOf(stubEntity)) + + localDataSource + .observeAll() + .test() + .assertNoErrors() + .assertValue { endpoints -> endpoints.size == 1 } + } + + @Test + fun `given attempt to observe all, dao returns empty list, expected empty domain list`() { + every { + stubDao.observeAll() + } returns Flowable.just(emptyList()) + + localDataSource + .observeAll() + .test() + .assertNoErrors() + .assertValue { endpoints -> endpoints.isEmpty() } + } + + @Test + fun `given attempt to get all, dao returns entities, expected domain models`() { + every { + stubDao.queryAll() + } returns Single.just(listOf(stubEntity)) + + localDataSource + .getAll() + .test() + .assertNoErrors() + .assertValue { endpoints -> endpoints.size == 1 } + .await() + .assertComplete() + } + + @Test + fun `given attempt to get all, dao throws exception, expected error value`() { + every { + stubDao.queryAll() + } returns Single.error(stubException) + + localDataSource + .getAll() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to get by id, dao returns entity, expected domain model`() { + val id = "fal-ai/flux/schnell" + + every { + stubDao.queryById(id) + } returns Single.just(stubEntity) + + localDataSource + .getById(id) + .test() + .assertNoErrors() + .assertValue { endpoint -> endpoint.id == id } + .await() + .assertComplete() + } + + @Test + fun `given attempt to get by id, dao throws exception, expected error value`() { + val id = "nonexistent-id" + + every { + stubDao.queryById(id) + } returns Single.error(stubException) + + localDataSource + .getById(id) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to save endpoint, dao insert success, expected complete value`() { + every { + stubDao.insert(any()) + } returns Completable.complete() + + localDataSource + .save(mockFalAiEndpoint) + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { stubDao.insert(any()) } + } + + @Test + fun `given attempt to save endpoint, dao insert fails, expected error value`() { + every { + stubDao.insert(any()) + } returns Completable.error(stubException) + + localDataSource + .save(mockFalAiEndpoint) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to delete endpoint, dao delete success, expected complete value`() { + val id = "fal-ai/flux/schnell" + + every { + stubDao.deleteById(id) + } returns Completable.complete() + + localDataSource + .delete(id) + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { stubDao.deleteById(id) } + } + + @Test + fun `given attempt to delete endpoint, dao delete fails, expected error value`() { + val id = "nonexistent-id" + + every { + stubDao.deleteById(id) + } returns Completable.error(stubException) + + localDataSource + .delete(id) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/GenerationResultLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/GenerationResultLocalDataSourceTest.kt similarity index 93% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/GenerationResultLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/GenerationResultLocalDataSourceTest.kt index 7b561a748..4531d5b47 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/GenerationResultLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/GenerationResultLocalDataSourceTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.data.local - -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResults -import com.shifthackz.aisdv1.data.mocks.mockGenerationResultEntities -import com.shifthackz.aisdv1.data.mocks.mockGenerationResultEntity -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.storage.db.persistent.dao.GenerationResultDao +package dev.minios.pdaiv1.data.local + +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResults +import dev.minios.pdaiv1.data.mocks.mockGenerationResultEntities +import dev.minios.pdaiv1.data.mocks.mockGenerationResultEntity +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.storage.db.persistent.dao.GenerationResultDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/HuggingFaceModelsLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/HuggingFaceModelsLocalDataSourceTest.kt similarity index 87% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/HuggingFaceModelsLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/HuggingFaceModelsLocalDataSourceTest.kt index 5248a1de0..e67a809fa 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/HuggingFaceModelsLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/HuggingFaceModelsLocalDataSourceTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mappers.mapEntityToDomain -import com.shifthackz.aisdv1.data.mocks.mockHuggingFaceModelEntities -import com.shifthackz.aisdv1.data.mocks.mockHuggingFaceModels -import com.shifthackz.aisdv1.storage.db.persistent.dao.HuggingFaceModelDao +import dev.minios.pdaiv1.data.mappers.mapEntityToDomain +import dev.minios.pdaiv1.data.mocks.mockHuggingFaceModelEntities +import dev.minios.pdaiv1.data.mocks.mockHuggingFaceModels +import dev.minios.pdaiv1.storage.db.persistent.dao.HuggingFaceModelDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/LorasLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/LorasLocalDataSourceTest.kt similarity index 92% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/LorasLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/LorasLocalDataSourceTest.kt index 20c265eb2..06d65e3ac 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/LorasLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/LorasLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionLoraEntities -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionLoras -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionLoraDao +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionLoraEntities +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionLoras +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionLoraDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/ServerConfigurationLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/ServerConfigurationLocalDataSourceTest.kt similarity index 88% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/ServerConfigurationLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/ServerConfigurationLocalDataSourceTest.kt index 8edcd977f..f54a6457a 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/ServerConfigurationLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/ServerConfigurationLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockServerConfiguration -import com.shifthackz.aisdv1.data.mocks.mockServerConfigurationEntity -import com.shifthackz.aisdv1.storage.db.cache.dao.ServerConfigurationDao +import dev.minios.pdaiv1.data.mocks.mockServerConfiguration +import dev.minios.pdaiv1.data.mocks.mockServerConfigurationEntity +import dev.minios.pdaiv1.storage.db.cache.dao.ServerConfigurationDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/StabilityAiCreditsLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/StabilityAiCreditsLocalDataSourceTest.kt similarity index 97% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/StabilityAiCreditsLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/StabilityAiCreditsLocalDataSourceTest.kt index b703cb986..74ea4cd3f 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/StabilityAiCreditsLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/StabilityAiCreditsLocalDataSourceTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local import io.reactivex.rxjava3.subjects.BehaviorSubject import org.junit.Test diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionHyperNetworksLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionHyperNetworksLocalDataSourceTest.kt similarity index 92% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionHyperNetworksLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionHyperNetworksLocalDataSourceTest.kt index be9daa461..067eb7d7d 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionHyperNetworksLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionHyperNetworksLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionHyperNetworkEntities -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionHyperNetworks -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionHyperNetworkDao +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionHyperNetworkEntities +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionHyperNetworks +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionHyperNetworkDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionModelsLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionModelsLocalDataSourceTest.kt similarity index 92% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionModelsLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionModelsLocalDataSourceTest.kt index c51db3fc9..c01a4e8ba 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionModelsLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionModelsLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionModelEntities -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionModels -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionModelDao +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionModelEntities +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionModels +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionModelDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionSamplersLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionSamplersLocalDataSourceTest.kt similarity index 92% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionSamplersLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionSamplersLocalDataSourceTest.kt index b7376d68c..cdb01ac5a 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/StableDiffusionSamplersLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/StableDiffusionSamplersLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionSamplerEntities -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionSamplers -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionSamplerDao +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionSamplerEntities +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionSamplers +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionSamplerDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/SupportersLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/SupportersLocalDataSourceTest.kt similarity index 93% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/SupportersLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/SupportersLocalDataSourceTest.kt index f9a2aea99..f9c94b4a2 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/SupportersLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/SupportersLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockSupporterEntities -import com.shifthackz.aisdv1.data.mocks.mockSupporters -import com.shifthackz.aisdv1.storage.db.persistent.dao.SupporterDao +import dev.minios.pdaiv1.data.mocks.mockSupporterEntities +import dev.minios.pdaiv1.data.mocks.mockSupporters +import dev.minios.pdaiv1.storage.db.persistent.dao.SupporterDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/local/SwarmUiModelsLocalDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/local/SwarmUiModelsLocalDataSourceTest.kt similarity index 93% rename from data/src/test/java/com/shifthackz/aisdv1/data/local/SwarmUiModelsLocalDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/local/SwarmUiModelsLocalDataSourceTest.kt index 34ebbe564..7a1ad01b4 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/local/SwarmUiModelsLocalDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/local/SwarmUiModelsLocalDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.local +package dev.minios.pdaiv1.data.local -import com.shifthackz.aisdv1.data.mocks.mockSwarmUiModelEntities -import com.shifthackz.aisdv1.data.mocks.mockSwarmUiModels -import com.shifthackz.aisdv1.storage.db.cache.dao.SwarmUiModelDao +import dev.minios.pdaiv1.data.mocks.mockSwarmUiModelEntities +import dev.minios.pdaiv1.data.mocks.mockSwarmUiModels +import dev.minios.pdaiv1.storage.db.cache.dao.SwarmUiModelDao import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/AiGenerationResultMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/AiGenerationResultMocks.kt new file mode 100644 index 000000000..ac8fe2f0b --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/AiGenerationResultMocks.kt @@ -0,0 +1,32 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.MediaType +import java.util.Date + +val mockAiGenerationResult = AiGenerationResult( + id = 5598L, + image = "img", + inputImage = "inp", + createdAt = Date(0), + type = AiGenerationResult.Type.IMAGE_TO_IMAGE, + prompt = "prompt", + negativePrompt = "negative", + width = 512, + height = 512, + samplingSteps = 7, + cfgScale = 0.7f, + restoreFaces = true, + sampler = "sampler", + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + denoisingStrength = 1504f, + hidden = false, + mediaPath = "", + inputMediaPath = "", + mediaType = MediaType.IMAGE, + modelName = "MockModel", +) + +val mockAiGenerationResults = listOf(mockAiGenerationResult) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/DownloadableModelResponseMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/DownloadableModelResponseMocks.kt new file mode 100644 index 000000000..142b70197 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/DownloadableModelResponseMocks.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.response.DownloadableModelResponse + +val mockDownloadableModelsResponse = listOf( + DownloadableModelResponse( + id = "1", + name = "Model 1", + size = "5 Gb", + sources = listOf("https://example.com/1.html"), + ) +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/FalAiMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/FalAiMocks.kt new file mode 100644 index 000000000..7067f6a3f --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/FalAiMocks.kt @@ -0,0 +1,53 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiEndpointCategory +import dev.minios.pdaiv1.domain.entity.FalAiEndpointSchema +import dev.minios.pdaiv1.domain.entity.FalAiInputProperty +import dev.minios.pdaiv1.domain.entity.FalAiPayload +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType + +val mockFalAiInputProperty = FalAiInputProperty( + name = "prompt", + title = "Prompt", + description = "The prompt to generate an image from", + type = FalAiPropertyType.STRING, + default = null, + minimum = null, + maximum = null, + enumValues = null, + isRequired = true, + isImageInput = false, +) + +val mockFalAiEndpointSchema = FalAiEndpointSchema( + baseUrl = "https://queue.fal.run", + submissionPath = "/fal-ai/flux/schnell", + inputProperties = listOf(mockFalAiInputProperty), + requiredProperties = listOf("prompt"), + propertyOrder = listOf("prompt"), +) + +val mockFalAiEndpoint = FalAiEndpoint( + id = "fal-ai/flux/schnell", + endpointId = "fal-ai/flux/schnell", + title = "FLUX.1 [schnell]", + description = "Fast text to image generation", + category = FalAiEndpointCategory.TEXT_TO_IMAGE, + group = "FLUX", + thumbnailUrl = "https://fal.ai/thumbnails/flux-schnell.jpg", + playgroundUrl = "https://fal.ai/models/fal-ai/flux/schnell", + documentationUrl = "https://fal.ai/models/fal-ai/flux/schnell/api", + isCustom = false, + schema = mockFalAiEndpointSchema, +) + +val mockFalAiEndpoints = listOf(mockFalAiEndpoint) + +val mockFalAiPayload = FalAiPayload( + endpointId = "fal-ai/flux/schnell", + parameters = mapOf( + "prompt" to "a beautiful sunset", + "num_inference_steps" to 4, + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/ForgeModuleMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ForgeModuleMocks.kt new file mode 100644 index 000000000..3c4c1d8a4 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ForgeModuleMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.ForgeModule + +val mockForgeModule = ForgeModule( + name = "ADetailer", + path = "extensions/adetailer", +) + +val mockForgeModules = listOf( + mockForgeModule, + ForgeModule( + name = "ControlNet", + path = "extensions/sd-webui-controlnet", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/GenerationResultEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/GenerationResultEntityMocks.kt new file mode 100644 index 000000000..b324cd333 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/GenerationResultEntityMocks.kt @@ -0,0 +1,34 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.storage.db.persistent.entity.GenerationResultEntity +import java.util.Date + +val mockGenerationResultEntity = GenerationResultEntity( + id = 5598L, + imageBase64 = "img", + originalImageBase64 = "inp", + createdAt = Date(0), + generationType = AiGenerationResult.Type.IMAGE_TO_IMAGE.key, + prompt = "prompt", + negativePrompt = "negative", + width = 512, + height = 512, + samplingSteps = 7, + cfgScale = 0.7f, + restoreFaces = true, + sampler = "sampler", + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + denoisingStrength = 1504f, + hidden = false, + liked = false, + mediaPath = "", + inputMediaPath = "", + mediaType = "IMAGE", + modelName = "MockModel", + blurHash = "", +) + +val mockGenerationResultEntities = listOf(mockGenerationResultEntity) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelEntityMocks.kt new file mode 100644 index 000000000..d0c2e3605 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelEntityMocks.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.persistent.entity.HuggingFaceModelEntity + +val mockHuggingFaceModelEntity = HuggingFaceModelEntity( + id = "050598", + name = "Super model", + alias = "❤", + source = "https://life.archive.org/models/unique/050598", +) + +val mockHuggingFaceModelEntities = listOf( + mockHuggingFaceModelEntity, +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelMocks.kt new file mode 100644 index 000000000..3ec0055e1 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelMocks.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel + +val mockHuggingFaceModel = HuggingFaceModel( + id = "050598", + name = "Super model", + alias = "❤", + source = "https://life.archive.org/models/unique/050598", +) + +val mockHuggingFaceModels = listOf(mockHuggingFaceModel) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelRawMocks.kt similarity index 79% rename from data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelRawMocks.kt rename to data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelRawMocks.kt index efb83fcf5..22f339d7b 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/HuggingFaceModelRawMocks.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/HuggingFaceModelRawMocks.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.data.mocks +package dev.minios.pdaiv1.data.mocks -import com.shifthackz.aisdv1.network.model.HuggingFaceModelRaw +import dev.minios.pdaiv1.network.model.HuggingFaceModelRaw val mockHuggingFaceModelsRaw = listOf( HuggingFaceModelRaw( diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/ImageToImagePayloadMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ImageToImagePayloadMocks.kt new file mode 100644 index 000000000..5b8a5f883 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ImageToImagePayloadMocks.kt @@ -0,0 +1,29 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload + +val mockImageToImagePayload = ImageToImagePayload( + base64Image = "", + base64MaskImage = "", + denoisingStrength = 7f, + prompt = "prompt", + negativePrompt = "negative", + samplingSteps = 12, + cfgScale = 0.7f, + width = 512, + height = 512, + restoreFaces = true, + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + sampler = "sampler", + nsfw = true, + batchCount = 1, + inPaintingMaskInvert = 0, + inPaintFullResPadding = 0, + inPaintingFill = 0, + inPaintFullRes = false, + maskBlur = 0, + stabilityAiClipGuidance = null, + stabilityAiStylePreset = null, +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/LocalAiModelMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/LocalAiModelMocks.kt new file mode 100644 index 000000000..e6574cdbe --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/LocalAiModelMocks.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.LocalAiModel + +val mockLocalAiModel = LocalAiModel( + id = "5598", + type = LocalAiModel.Type.ONNX, + name = "Model 5598", + size = "5 Gb", + sources = listOf("https://example.com/1.html"), + downloaded = false, + selected = false, +) + +val mockLocalAiModels = listOf(mockLocalAiModel) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/LocalModelEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/LocalModelEntityMocks.kt similarity index 79% rename from data/src/test/java/com/shifthackz/aisdv1/data/mocks/LocalModelEntityMocks.kt rename to data/src/test/java/dev/minios/pdaiv1/data/mocks/LocalModelEntityMocks.kt index 7beaa4beb..6aae5ca7d 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/LocalModelEntityMocks.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/LocalModelEntityMocks.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.data.mocks +package dev.minios.pdaiv1.data.mocks -import com.shifthackz.aisdv1.storage.db.persistent.entity.LocalModelEntity +import dev.minios.pdaiv1.storage.db.persistent.entity.LocalModelEntity val mockLocalModelEntity = LocalModelEntity( id = "5598", diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/OpenAiResponseMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/OpenAiResponseMocks.kt new file mode 100644 index 000000000..8a435f396 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/OpenAiResponseMocks.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.OpenAiImageRaw +import dev.minios.pdaiv1.network.response.OpenAiResponse + +val mockSuccessOpenAiResponse = OpenAiResponse( + created = System.currentTimeMillis(), + data = listOf( + OpenAiImageRaw( + "base64", + "https://openai.com", + "prompt", + ), + ), +) + +val mockBadOpenAiResponse = OpenAiResponse( + created = System.currentTimeMillis(), + data = emptyList(), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SdEmbeddingsResponseMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SdEmbeddingsResponseMocks.kt new file mode 100644 index 000000000..a4c3e90f1 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SdEmbeddingsResponseMocks.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.response.SdEmbeddingsResponse + +val mockSdEmbeddingsResponse = SdEmbeddingsResponse( + loaded = mapOf("1504" to "5598"), +) + +val mockEmptySdEmbeddingsResponse = SdEmbeddingsResponse( + loaded = emptyMap(), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SdGenerationResponseMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SdGenerationResponseMocks.kt new file mode 100644 index 000000000..900f38c25 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SdGenerationResponseMocks.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.response.SdGenerationResponse + +val mockSdGenerationResponse = SdGenerationResponse( + images = listOf("base64"), + info = "info", +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationEntityMocks.kt new file mode 100644 index 000000000..1a7c68550 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationEntityMocks.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.cache.entity.ServerConfigurationEntity + +val mockServerConfigurationEntity = ServerConfigurationEntity( + serverId = "5598", + sdModelCheckpoint = "checkpoint", +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationMocks.kt new file mode 100644 index 000000000..c537421f7 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationMocks.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.ServerConfiguration + +val mockServerConfiguration = ServerConfiguration("checkpoint") diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationRawMocks.kt new file mode 100644 index 000000000..3bfd8cfa7 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/ServerConfigurationRawMocks.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.ServerConfigurationRaw + +val mockServerConfigurationRaw = ServerConfigurationRaw("5598") diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityAiEngineMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityAiEngineMocks.kt new file mode 100644 index 000000000..c7f14641c --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityAiEngineMocks.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine + +val mockStabilityAiEngines = listOf( + StabilityAiEngine( + id = "5598", + name = "engine_5598", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityAiEngineRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityAiEngineRawMocks.kt new file mode 100644 index 000000000..6452877a9 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityAiEngineRawMocks.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.StabilityAiEngineRaw + +val mockStabilityAiEnginesRaw = listOf( + StabilityAiEngineRaw( + description = "❤", + id = "5598", + name = "Super engine", + type = "unique", + ), +) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityGenerationResponseMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityGenerationResponseMocks.kt similarity index 75% rename from data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityGenerationResponseMocks.kt rename to data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityGenerationResponseMocks.kt index 5199d5198..fd621d026 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StabilityGenerationResponseMocks.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StabilityGenerationResponseMocks.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.data.mocks +package dev.minios.pdaiv1.data.mocks -import com.shifthackz.aisdv1.network.response.StabilityGenerationResponse +import dev.minios.pdaiv1.network.response.StabilityGenerationResponse val mockStabilityGenerationResponse = StabilityGenerationResponse( artifacts = listOf( diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionEmbeddingEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionEmbeddingEntityMocks.kt new file mode 100644 index 000000000..fb7388255 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionEmbeddingEntityMocks.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity + +val mockStableDiffusionEmbeddingEntities = listOf( + StableDiffusionEmbeddingEntity( + id = "5598", + keyword = "keyword_5598", + ), + StableDiffusionEmbeddingEntity( + id = "151297", + keyword = "keyword_151297", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionEmbeddingMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionEmbeddingMocks.kt new file mode 100644 index 000000000..16edbc7d6 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionEmbeddingMocks.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.Embedding + +val mockEmbeddings = listOf( + Embedding("keyword_5598"), + Embedding("keyword_151297"), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkEntityMocks.kt new file mode 100644 index 000000000..7965534d2 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkEntityMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity + +val mockStableDiffusionHyperNetworkEntities = listOf( + StableDiffusionHyperNetworkEntity( + id = "5598", + name = "net_5598", + path = "/unknown", + ), + StableDiffusionHyperNetworkEntity( + id = "151297", + name = "net_151297", + path = "/unknown", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkMocks.kt new file mode 100644 index 000000000..654affa17 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkMocks.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork + +val mockStableDiffusionHyperNetworks = listOf( + StableDiffusionHyperNetwork( + name = "net_5598", + path = "/unknown", + ), + StableDiffusionHyperNetwork( + name = "net_151297", + path = "/unknown", + ) +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkRawMocks.kt new file mode 100644 index 000000000..59577d98f --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionHyperNetworkRawMocks.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.StableDiffusionHyperNetworkRaw + +val mockStableDiffusionHyperNetworkRaw = listOf( + StableDiffusionHyperNetworkRaw( + name = "5598", + path = "Unknown", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraEntityMocks.kt new file mode 100644 index 000000000..bc7e79b2e --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraEntityMocks.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionLoraEntity + +val mockStableDiffusionLoraEntities = listOf( + StableDiffusionLoraEntity( + id = "5598", + name = "name_5598", + alias = "alias_5598", + path = "/unknown", + ), + StableDiffusionLoraEntity( + id = "151297", + name = "name_151297", + alias = "alias_151297", + path = "/unknown", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraMocks.kt new file mode 100644 index 000000000..86020afce --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.LoRA + +val mockStableDiffusionLoras = listOf( + LoRA( + name = "name_5598", + alias = "alias_5598", + path = "/unknown", + ), + LoRA( + name = "name_151297", + alias = "alias_151297", + path = "/unknown", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraRawMocks.kt new file mode 100644 index 000000000..ce2e4ae04 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionLoraRawMocks.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.StableDiffusionLoraRaw + +val mockStableDiffusionLoraRaw = listOf( + StableDiffusionLoraRaw( + name = "Super lora", + alias = "5598", + path = "Unknown", + ), +) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelEntityMocks.kt similarity index 81% rename from data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelEntityMocks.kt rename to data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelEntityMocks.kt index 51e9496ca..380772a8d 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/StableDiffusionModelEntityMocks.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelEntityMocks.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.data.mocks +package dev.minios.pdaiv1.data.mocks -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionModelEntity +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionModelEntity val mockStableDiffusionModelEntities = listOf( StableDiffusionModelEntity( diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelMocks.kt new file mode 100644 index 000000000..ff9d97141 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelMocks.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel + +val mockStableDiffusionModels = listOf( + StableDiffusionModel( + title = "title_5598", + modelName = "name_5598", + hash = "hash_5598", + sha256 = "sha_5598", + filename = "file_5598", + config = "config_5598", + ), + StableDiffusionModel( + title = "title_151297", + modelName = "name_151297", + hash = "hash_151297", + sha256 = "sha_151297", + filename = "file_151297", + config = "config_151297", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelRawMocks.kt new file mode 100644 index 000000000..51ddc847d --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionModelRawMocks.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.StableDiffusionModelRaw + +val mockStableDiffusionModelRaw = listOf( + StableDiffusionModelRaw( + title = "5598", + modelName = "5598", + hash = "hash5598", + sha256 = "sha5598", + filename = "Unknown", + config = "Unconfigurable", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerEntityMocks.kt new file mode 100644 index 000000000..9be32f7a6 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerEntityMocks.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionSamplerEntity + +val mockStableDiffusionSamplerEntities = listOf( + StableDiffusionSamplerEntity( + id = "5598", + name = "name_5598", + aliases = emptyList(), + options = emptyMap(), + ), + StableDiffusionSamplerEntity( + id = "151297", + name = "name_151297", + aliases = emptyList(), + options = emptyMap(), + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerMocks.kt new file mode 100644 index 000000000..dadda0c53 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler + +val mockStableDiffusionSamplers = listOf( + StableDiffusionSampler( + name = "name_5598", + aliases = emptyList(), + options = emptyMap(), + ), + StableDiffusionSampler( + name = "name_151297", + aliases = emptyList(), + options = emptyMap(), + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerRawMocks.kt new file mode 100644 index 000000000..337557e9e --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/StableDiffusionSamplerRawMocks.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.StableDiffusionSamplerRaw + +val mockStableDiffusionSamplerRaw = listOf( + StableDiffusionSamplerRaw( + "5598", + listOf(), + mapOf(), + ) +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterEntityMocks.kt new file mode 100644 index 000000000..9bff43da0 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterEntityMocks.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.persistent.entity.SupporterEntity +import java.util.Date + +val mockSupporterEntities = listOf( + SupporterEntity( + id = 5598, + name = "NZ", + date = Date(1998, 5, 5), + message = "I always wanted support you ❤", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterMocks.kt new file mode 100644 index 000000000..b1ac6a1b2 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterMocks.kt @@ -0,0 +1,15 @@ +@file:Suppress("DEPRECATION") + +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.Supporter +import java.util.Date + +val mockSupporters = listOf( + Supporter( + id = 5598, + name = "NZ", + date = Date(1998, 5, 5), + message = "I always wanted support you ❤", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterRawMocks.kt new file mode 100644 index 000000000..2063bb77f --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SupporterRawMocks.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.SupporterRaw + +val mockSupportersRaw = listOf( + SupporterRaw( + id = 5598, + name = "NZ", + date = "1998-05-05", + message = "I always wanted support you ❤", + type = "bmc", + amount = "55.98", + currency = "USD", + ), +) \ No newline at end of file diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiGenerationResponseMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiGenerationResponseMocks.kt new file mode 100644 index 000000000..ba95780b5 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiGenerationResponseMocks.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.response.SwarmUiGenerationResponse + +val mockSwarmUiGenerationResponse = SwarmUiGenerationResponse( + images = listOf("/tmp/img.jpg"), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelEntityMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelEntityMocks.kt new file mode 100644 index 000000000..b280b8518 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelEntityMocks.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.storage.db.cache.entity.SwarmUiModelEntity + +val mockSwarmUiModelEntities = listOf( + SwarmUiModelEntity( + id = "5598", + name = "5598", + title = "5598", + author = "", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelMocks.kt new file mode 100644 index 000000000..266a778fb --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelMocks.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.SwarmUiModel + +val mockSwarmUiModels = listOf( + SwarmUiModel( + name = "5598", + title = "5598", + author = "5598", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelRawMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelRawMocks.kt new file mode 100644 index 000000000..64b2bfab7 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/SwarmUiModelRawMocks.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.network.model.SwarmUiModelRaw + +val mockSwarmUiModelsRaw = listOf( + SwarmUiModelRaw( + name = "5598", + title = "5598", + author = "5598", + ), +) diff --git a/data/src/test/java/dev/minios/pdaiv1/data/mocks/TextToImagePayloadMocks.kt b/data/src/test/java/dev/minios/pdaiv1/data/mocks/TextToImagePayloadMocks.kt new file mode 100644 index 000000000..b16de74e7 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/mocks/TextToImagePayloadMocks.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.data.mocks + +import dev.minios.pdaiv1.domain.entity.TextToImagePayload + +val mockTextToImagePayload = TextToImagePayload( + prompt = "prompt", + negativePrompt = "negative", + samplingSteps = 12, + cfgScale = 0.7f, + width = 512, + height = 512, + restoreFaces = true, + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + sampler = "sampler", + nsfw = true, + batchCount = 1, + quality = null, + style = null, + openAiModel = null, + stabilityAiClipGuidance = null, + stabilityAiStylePreset = null, +) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/preference/PreferenceManagerImplTest.kt similarity index 87% rename from data/src/test/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/preference/PreferenceManagerImplTest.kt index 5f68e23b3..16081be27 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/preference/PreferenceManagerImplTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.data.preference +package dev.minios.pdaiv1.data.preference import android.content.SharedPreferences import com.nhaarman.mockitokotlin2.any @@ -6,33 +6,33 @@ import com.nhaarman.mockitokotlin2.doNothing import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_AI_AUTO_SAVE -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_DEMO_MODE -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_COLOR_TOKEN -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_DARK_THEME -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_DARK_TOKEN -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_DYNAMIC_COLORS -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_SYSTEM_DARK_THEME -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_FORCE_SETUP_AFTER_UPDATE -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_FORM_ALWAYS_SHOW_ADVANCED_OPTIONS -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_FORM_PROMPT_TAGGED_INPUT -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_HORDE_API_KEY -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_HUGGING_FACE_API_KEY -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_HUGGING_FACE_MODEL_KEY -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_LOCAL_MODEL_ID -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_LOCAL_NN_API -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_MONITOR_CONNECTIVITY -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_OPEN_AI_API_KEY -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_SAVE_TO_MEDIA_STORE -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_SD_MODEL -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_SERVER_SOURCE -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_SERVER_URL -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_STABILITY_AI_API_KEY -import com.shifthackz.aisdv1.data.preference.PreferenceManagerImpl.Companion.KEY_STABILITY_AI_ENGINE_ID_KEY -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import com.shifthackz.aisdv1.domain.entity.ServerSource +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_AI_AUTO_SAVE +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_DEMO_MODE +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_COLOR_TOKEN +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_DARK_THEME +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_DARK_TOKEN +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_DYNAMIC_COLORS +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_DESIGN_SYSTEM_DARK_THEME +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_FORCE_SETUP_AFTER_UPDATE +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_FORM_ALWAYS_SHOW_ADVANCED_OPTIONS +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_FORM_PROMPT_TAGGED_INPUT +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_HORDE_API_KEY +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_HUGGING_FACE_API_KEY +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_HUGGING_FACE_MODEL_KEY +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_LOCAL_MODEL_ID +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_LOCAL_NN_API +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_MONITOR_CONNECTIVITY +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_OPEN_AI_API_KEY +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_SAVE_TO_MEDIA_STORE +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_SD_MODEL +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_SERVER_SOURCE +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_SERVER_URL +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_STABILITY_AI_API_KEY +import dev.minios.pdaiv1.data.preference.PreferenceManagerImpl.Companion.KEY_STABILITY_AI_ENGINE_ID_KEY +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.domain.entity.ServerSource import org.junit.Assert import org.junit.Before import org.junit.Test diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/preference/SessionPreferenceImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/preference/SessionPreferenceImplTest.kt similarity index 93% rename from data/src/test/java/com/shifthackz/aisdv1/data/preference/SessionPreferenceImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/preference/SessionPreferenceImplTest.kt index 2e645a64e..4dcd63a23 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/preference/SessionPreferenceImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/preference/SessionPreferenceImplTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.data.preference +package dev.minios.pdaiv1.data.preference import org.junit.Assert import org.junit.Test diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/DownloadableModelRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/DownloadableModelRemoteDataSourceTest.kt similarity index 75% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/DownloadableModelRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/DownloadableModelRemoteDataSourceTest.kt index 775fd5a6a..c8588d647 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/DownloadableModelRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/DownloadableModelRemoteDataSourceTest.kt @@ -1,12 +1,12 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.data.mappers.mapRawToCheckpointDomain -import com.shifthackz.aisdv1.data.mocks.mockDownloadableModelsResponse -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.network.api.sdai.DownloadableModelsApi +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.data.mappers.mapRawToCheckpointDomain +import dev.minios.pdaiv1.data.mocks.mockDownloadableModelsResponse +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.network.api.pdai.DownloadableModelsApi import io.reactivex.rxjava3.core.Single import org.junit.Test @@ -29,9 +29,13 @@ class DownloadableModelRemoteDataSourceTest { whenever(stubApi.fetchMediaPipeModels()) .thenReturn(Single.just(mockDownloadableModelsResponse)) + whenever(stubApi.fetchQnnModels()) + .thenReturn(Single.just(mockDownloadableModelsResponse)) + val expected = listOf( mockDownloadableModelsResponse.mapRawToCheckpointDomain(LocalAiModel.Type.ONNX), mockDownloadableModelsResponse.mapRawToCheckpointDomain(LocalAiModel.Type.MediaPipe), + mockDownloadableModelsResponse.mapRawToCheckpointDomain(LocalAiModel.Type.QNN), ).flatten() remoteDataSource @@ -51,6 +55,9 @@ class DownloadableModelRemoteDataSourceTest { whenever(stubApi.fetchMediaPipeModels()) .thenReturn(Single.just(emptyList())) + whenever(stubApi.fetchQnnModels()) + .thenReturn(Single.just(emptyList())) + remoteDataSource .fetch() .test() @@ -68,6 +75,9 @@ class DownloadableModelRemoteDataSourceTest { whenever(stubApi.fetchMediaPipeModels()) .thenReturn(Single.error(stubException)) + whenever(stubApi.fetchQnnModels()) + .thenReturn(Single.error(stubException)) + remoteDataSource .fetch() .test() diff --git a/data/src/test/java/dev/minios/pdaiv1/data/remote/FalAiEndpointRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/FalAiEndpointRemoteDataSourceTest.kt new file mode 100644 index 000000000..bf78f584d --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/FalAiEndpointRemoteDataSourceTest.kt @@ -0,0 +1,84 @@ +package dev.minios.pdaiv1.data.remote + +import io.mockk.every +import io.mockk.mockk +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.Test + +class FalAiEndpointRemoteDataSourceTest { + + private val stubHttpClient = mockk() + private val stubCall = mockk() + + private val remoteDataSource = FalAiEndpointRemoteDataSource(stubHttpClient) + + @Test + fun `given attempt to fetch from url, http returns error response, expected IllegalStateException`() { + val url = "https://fal.ai/models/test-model/openapi.json" + + val errorResponse = Response.Builder() + .request(Request.Builder().url(url).build()) + .protocol(Protocol.HTTP_1_1) + .code(404) + .message("Not Found") + .body("Not found".toResponseBody()) + .build() + + every { stubHttpClient.newCall(any()) } returns stubCall + every { stubCall.execute() } returns errorResponse + + remoteDataSource + .fetchFromUrl(url) + .test() + .assertError { error -> + error is IllegalStateException && + error.message?.contains("Failed to fetch OpenAPI schema: 404") == true + } + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to fetch from url, http returns empty body string, expected parse error`() { + val url = "https://fal.ai/models/test-model/openapi.json" + + val emptyResponse = Response.Builder() + .request(Request.Builder().url(url).build()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body("".toResponseBody()) + .build() + + every { stubHttpClient.newCall(any()) } returns stubCall + every { stubCall.execute() } returns emptyResponse + + remoteDataSource + .fetchFromUrl(url) + .test() + .assertError { error -> error is Exception } + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to fetch from url, http call throws exception, expected error value`() { + val url = "https://fal.ai/models/test-model/openapi.json" + val stubException = RuntimeException("Network error") + + every { stubHttpClient.newCall(any()) } returns stubCall + every { stubCall.execute() } throws stubException + + remoteDataSource + .fetchFromUrl(url) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/data/src/test/java/dev/minios/pdaiv1/data/remote/FalAiGenerationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/FalAiGenerationRemoteDataSourceTest.kt new file mode 100644 index 000000000..d0738a96b --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/FalAiGenerationRemoteDataSourceTest.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.network.api.falai.FalAiApi +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.core.Single +import okhttp3.OkHttpClient +import org.junit.Test + +class FalAiGenerationRemoteDataSourceTest { + + private val stubException = Throwable("API error") + private val stubApi = mockk() + private val stubHttpClient = mockk() + private val stubMediaFileManager = mockk() + + private val remoteDataSource = FalAiGenerationRemoteDataSource( + api = stubApi, + httpClient = stubHttpClient, + mediaFileManager = stubMediaFileManager, + ) + + @Test + fun `given attempt to validate API key, api returns success, expected true value`() { + val mockResponse = mockk() + + every { + stubApi.listModels(limit = 1) + } returns Single.just(mockResponse) + + remoteDataSource + .validateApiKey() + .test() + .assertNoErrors() + .assertValue(true) + .await() + .assertComplete() + } + + @Test + fun `given attempt to validate API key, api returns error, expected false value`() { + every { + stubApi.listModels(limit = 1) + } returns Single.error(stubException) + + remoteDataSource + .validateApiKey() + .test() + .assertNoErrors() + .assertValue(false) + .await() + .assertComplete() + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/HordeGenerationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/HordeGenerationRemoteDataSourceTest.kt similarity index 85% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/HordeGenerationRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/HordeGenerationRemoteDataSourceTest.kt index fe6cc4a01..8a6e7f16d 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/HordeGenerationRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/HordeGenerationRemoteDataSourceTest.kt @@ -1,16 +1,16 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote import android.graphics.Bitmap import android.graphics.BitmapFactory -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.domain.datasource.HordeGenerationDataSource -import com.shifthackz.aisdv1.network.api.horde.HordeRestApi -import com.shifthackz.aisdv1.network.response.HordeGenerationAsyncResponse -import com.shifthackz.aisdv1.network.response.HordeGenerationCheckFullResponse -import com.shifthackz.aisdv1.network.response.HordeGenerationCheckResponse -import com.shifthackz.aisdv1.network.response.HordeUserResponse +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.domain.datasource.HordeGenerationDataSource +import dev.minios.pdaiv1.network.api.horde.HordeRestApi +import dev.minios.pdaiv1.network.response.HordeGenerationAsyncResponse +import dev.minios.pdaiv1.network.response.HordeGenerationCheckFullResponse +import dev.minios.pdaiv1.network.response.HordeGenerationCheckResponse +import dev.minios.pdaiv1.network.response.HordeUserResponse import io.mockk.every import io.mockk.mockk import io.mockk.mockkConstructor diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/HuggingFaceGenerationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/HuggingFaceGenerationRemoteDataSourceTest.kt similarity index 91% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/HuggingFaceGenerationRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/HuggingFaceGenerationRemoteDataSourceTest.kt index 43606065f..828b3a622 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/HuggingFaceGenerationRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/HuggingFaceGenerationRemoteDataSourceTest.kt @@ -1,12 +1,12 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.network.api.huggingface.HuggingFaceApi -import com.shifthackz.aisdv1.network.api.huggingface.HuggingFaceInferenceApi +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.network.api.huggingface.HuggingFaceApi +import dev.minios.pdaiv1.network.api.huggingface.HuggingFaceInferenceApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/HuggingFaceModelsRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/HuggingFaceModelsRemoteDataSourceTest.kt similarity index 88% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/HuggingFaceModelsRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/HuggingFaceModelsRemoteDataSourceTest.kt index 4588f1147..19834de2f 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/HuggingFaceModelsRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/HuggingFaceModelsRemoteDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockHuggingFaceModelsRaw -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import com.shifthackz.aisdv1.network.api.sdai.HuggingFaceModelsApi +import dev.minios.pdaiv1.data.mocks.mockHuggingFaceModelsRaw +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.network.api.pdai.HuggingFaceModelsApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/OpenAiGenerationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/OpenAiGenerationRemoteDataSourceTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/OpenAiGenerationRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/OpenAiGenerationRemoteDataSourceTest.kt index a4f7069f0..54948ba39 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/OpenAiGenerationRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/OpenAiGenerationRemoteDataSourceTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockBadOpenAiResponse -import com.shifthackz.aisdv1.data.mocks.mockSuccessOpenAiResponse -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.network.api.openai.OpenAiApi -import com.shifthackz.aisdv1.network.response.OpenAiResponse +import dev.minios.pdaiv1.data.mocks.mockBadOpenAiResponse +import dev.minios.pdaiv1.data.mocks.mockSuccessOpenAiResponse +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.network.api.openai.OpenAiApi +import dev.minios.pdaiv1.network.response.OpenAiResponse import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/RandomImageRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/RandomImageRemoteDataSourceTest.kt similarity index 92% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/RandomImageRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/RandomImageRemoteDataSourceTest.kt index c8cf8151b..4c07db8f2 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/RandomImageRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/RandomImageRemoteDataSourceTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote import android.graphics.Bitmap -import com.shifthackz.aisdv1.network.api.imagecdn.ImageCdnRestApi +import dev.minios.pdaiv1.network.api.imagecdn.ImageCdnRestApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/dev/minios/pdaiv1/data/remote/ReportRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/ReportRemoteDataSourceTest.kt new file mode 100644 index 000000000..57b76fa59 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/ReportRemoteDataSourceTest.kt @@ -0,0 +1,86 @@ +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.network.api.pdai.ReportApi +import dev.minios.pdaiv1.network.request.ReportRequest +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Completable +import org.junit.Test + +class ReportRemoteDataSourceTest { + + private val stubException = Throwable("Network error") + private val stubApi = mockk() + + private val remoteDataSource = ReportRemoteDataSource(stubApi) + + @Test + fun `given attempt to send report, api returns success, expected complete value`() { + val text = "Bug report text" + val reason = ReportReason.Other + val image = "base64image" + val source = "HUGGING_FACE" + val model = "stabilityai/sdxl-turbo" + + every { + stubApi.postReport(any()) + } returns Completable.complete() + + remoteDataSource + .send(text, reason, image, source, model) + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { + stubApi.postReport(ReportRequest(text, reason.toString(), image, source, model)) + } + } + + @Test + fun `given attempt to send report, api returns error, expected error value`() { + val text = "Feature request" + val reason = ReportReason.InappropriateContent + val image = "" + val source = "LOCAL_MICROSOFT_ONNX" + val model = "onnx-model" + + every { + stubApi.postReport(any()) + } returns Completable.error(stubException) + + remoteDataSource + .send(text, reason, image, source, model) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to send report with empty image, api receives correct payload`() { + val text = "Some report" + val reason = ReportReason.Other + val image = "" + val source = "AUTOMATIC1111" + val model = "" + + every { + stubApi.postReport(any()) + } returns Completable.complete() + + remoteDataSource + .send(text, reason, image, source, model) + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { + stubApi.postReport(ReportRequest(text, reason.toString(), image, source, model)) + } + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/ServerConfigurationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/ServerConfigurationRemoteDataSourceTest.kt similarity index 88% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/ServerConfigurationRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/ServerConfigurationRemoteDataSourceTest.kt index b3edaaccf..cb3017913 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/ServerConfigurationRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/ServerConfigurationRemoteDataSourceTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockServerConfigurationRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.data.mocks.mockServerConfigurationRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiCreditsRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiCreditsRemoteDataSourceTest.kt similarity index 91% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiCreditsRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiCreditsRemoteDataSourceTest.kt index c621f5fbf..cf496b582 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiCreditsRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiCreditsRemoteDataSourceTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.network.api.stabilityai.StabilityAiApi -import com.shifthackz.aisdv1.network.response.StabilityCreditsResponse +import dev.minios.pdaiv1.network.api.stabilityai.StabilityAiApi +import dev.minios.pdaiv1.network.response.StabilityCreditsResponse import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiEnginesRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiEnginesRemoteDataSourceTest.kt similarity index 88% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiEnginesRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiEnginesRemoteDataSourceTest.kt index 57f97915a..d337da1af 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiEnginesRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiEnginesRemoteDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockStabilityAiEnginesRaw -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine -import com.shifthackz.aisdv1.network.api.stabilityai.StabilityAiApi +import dev.minios.pdaiv1.data.mocks.mockStabilityAiEnginesRaw +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine +import dev.minios.pdaiv1.network.api.stabilityai.StabilityAiApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiGenerationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiGenerationRemoteDataSourceTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiGenerationRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiGenerationRemoteDataSourceTest.kt index 04665a148..20e44a27c 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StabilityAiGenerationRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StabilityAiGenerationRemoteDataSourceTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockBadStabilityGenerationResponse -import com.shifthackz.aisdv1.data.mocks.mockStabilityGenerationResponse -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.network.api.stabilityai.StabilityAiApi -import com.shifthackz.aisdv1.network.error.StabilityAiErrorMapper +import dev.minios.pdaiv1.data.mocks.mockBadStabilityGenerationResponse +import dev.minios.pdaiv1.data.mocks.mockStabilityGenerationResponse +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.network.api.stabilityai.StabilityAiApi +import dev.minios.pdaiv1.network.error.StabilityAiErrorMapper import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionEmbeddingsRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionEmbeddingsRemoteDataSourceTest.kt similarity index 84% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionEmbeddingsRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionEmbeddingsRemoteDataSourceTest.kt index 945287115..aee2e8912 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionEmbeddingsRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionEmbeddingsRemoteDataSourceTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockEmptySdEmbeddingsResponse -import com.shifthackz.aisdv1.data.mocks.mockSdEmbeddingsResponse -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.data.mocks.mockEmptySdEmbeddingsResponse +import dev.minios.pdaiv1.data.mocks.mockSdEmbeddingsResponse +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionGenerationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionGenerationRemoteDataSourceTest.kt similarity index 89% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionGenerationRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionGenerationRemoteDataSourceTest.kt index b38b133f8..f9d1a6251 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionGenerationRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionGenerationRemoteDataSourceTest.kt @@ -1,12 +1,12 @@ -package com.shifthackz.aisdv1.data.remote - -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockSdGenerationResponse -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.response.SdGenerationResponse +package dev.minios.pdaiv1.data.remote + +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockSdGenerationResponse +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.response.SdGenerationResponse import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionHyperNetworksRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionHyperNetworksRemoteDataSourceTest.kt similarity index 84% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionHyperNetworksRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionHyperNetworksRemoteDataSourceTest.kt index 610b95d7f..93fd4e76f 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionHyperNetworksRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionHyperNetworksRemoteDataSourceTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionHyperNetworkRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.model.StableDiffusionHyperNetworkRaw +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionHyperNetworkRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.model.StableDiffusionHyperNetworkRaw import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionLorasRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionLorasRemoteDataSourceTest.kt similarity index 87% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionLorasRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionLorasRemoteDataSourceTest.kt index a3e129ef3..68e044f4f 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionLorasRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionLorasRemoteDataSourceTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionLoraRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionLoraRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionModelsRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionModelsRemoteDataSourceTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionModelsRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionModelsRemoteDataSourceTest.kt index 6cbb03c67..d4c5cc73f 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionModelsRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionModelsRemoteDataSourceTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionModelRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionModelRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionSamplersRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionSamplersRemoteDataSourceTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionSamplersRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionSamplersRemoteDataSourceTest.kt index f8a757ebc..39e5d309d 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/StableDiffusionSamplersRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/StableDiffusionSamplersRemoteDataSourceTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionSamplerRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionSamplerRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SupportersRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/SupportersRemoteDataSourceTest.kt similarity index 87% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/SupportersRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/SupportersRemoteDataSourceTest.kt index 5430eaf5f..f890390cb 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SupportersRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/SupportersRemoteDataSourceTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockSupporters -import com.shifthackz.aisdv1.data.mocks.mockSupportersRaw -import com.shifthackz.aisdv1.network.api.sdai.DonateApi +import dev.minios.pdaiv1.data.mocks.mockSupporters +import dev.minios.pdaiv1.data.mocks.mockSupportersRaw +import dev.minios.pdaiv1.network.api.pdai.DonateApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiEmbeddingsRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiEmbeddingsRemoteDataSourceTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiEmbeddingsRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiEmbeddingsRemoteDataSourceTest.kt index d0fbe69d9..761043829 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiEmbeddingsRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiEmbeddingsRemoteDataSourceTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockSwarmUiModelsRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse +import dev.minios.pdaiv1.data.mocks.mockSwarmUiModelsRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiGenerationRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiGenerationRemoteDataSourceTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiGenerationRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiGenerationRemoteDataSourceTest.kt index 29c431f4b..1b5f8583e 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiGenerationRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiGenerationRemoteDataSourceTest.kt @@ -1,14 +1,14 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.imageprocessing.Base64EncodingConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockSwarmUiGenerationResponse -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.core.imageprocessing.Base64EncodingConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockSwarmUiGenerationResponse +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiLorasRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiLorasRemoteDataSourceTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiLorasRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiLorasRemoteDataSourceTest.kt index ed70d23e3..e85e907fd 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiLorasRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiLorasRemoteDataSourceTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockSwarmUiModelsRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse +import dev.minios.pdaiv1.data.mocks.mockSwarmUiModelsRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiModelsRemoteDataSourceTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiModelsRemoteDataSourceTest.kt similarity index 85% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiModelsRemoteDataSourceTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiModelsRemoteDataSourceTest.kt index e4a68d0d6..a8524f3d6 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiModelsRemoteDataSourceTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiModelsRemoteDataSourceTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.mocks.mockSwarmUiModelsRaw -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse +import dev.minios.pdaiv1.data.mocks.mockSwarmUiModelsRaw +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiSessionDataSourceImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiSessionDataSourceImplTest.kt similarity index 89% rename from data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiSessionDataSourceImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiSessionDataSourceImplTest.kt index 6a1a3aa37..463caacc4 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/remote/SwarmUiSessionDataSourceImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/remote/SwarmUiSessionDataSourceImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.remote +package dev.minios.pdaiv1.data.remote -import com.shifthackz.aisdv1.data.provider.ServerUrlProvider -import com.shifthackz.aisdv1.domain.preference.SessionPreference -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.response.SwarmUiSessionResponse +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.preference.SessionPreference +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.response.SwarmUiSessionResponse import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/DownloadableModelRepositoryImplTest.kt similarity index 93% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/DownloadableModelRepositoryImplTest.kt index b246a019f..863f76150 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/DownloadableModelRepositoryImplTest.kt @@ -1,12 +1,12 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.data.mocks.mockLocalAiModel -import com.shifthackz.aisdv1.data.mocks.mockLocalAiModels -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.LocalAiModel +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.data.mocks.mockLocalAiModel +import dev.minios.pdaiv1.data.mocks.mockLocalAiModels +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.BackpressureStrategy @@ -226,7 +226,7 @@ class DownloadableModelRepositoryImplTest { } returns Single.just(mockLocalAiModel) val stubObserver = repository - .download("5598", "https://moroz.cc/stub.zip") + .download("5598", "https://example.com/stub.zip") .test() stubDownloadState.onNext(DownloadState.Unknown) @@ -261,7 +261,7 @@ class DownloadableModelRepositoryImplTest { } returns Single.just(mockLocalAiModel) val stubObserver = repository - .download("5598", "https://moroz.cc/stub.zip") + .download("5598", "https://example.com/stub.zip") .test() stubDownloadState.onNext(DownloadState.Unknown) @@ -294,7 +294,7 @@ class DownloadableModelRepositoryImplTest { } returns Observable.error(stubException) repository - .download("5598", "https://moroz.cc/stub.zip") + .download("5598", "https://example.com/stub.zip") .test() .assertError(stubException) .assertNoValues() diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/EmbeddingsRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/EmbeddingsRepositoryImplTest.kt similarity index 97% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/EmbeddingsRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/EmbeddingsRepositoryImplTest.kt index 74e044b10..1ff3f5f63 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/EmbeddingsRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/EmbeddingsRepositoryImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockEmbeddings -import com.shifthackz.aisdv1.domain.datasource.EmbeddingsDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.data.mocks.mockEmbeddings +import dev.minios.pdaiv1.domain.datasource.EmbeddingsDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/dev/minios/pdaiv1/data/repository/FalAiEndpointRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/FalAiEndpointRepositoryImplTest.kt new file mode 100644 index 000000000..018f39e63 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/FalAiEndpointRepositoryImplTest.kt @@ -0,0 +1,253 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.data.mocks.mockFalAiEndpoint +import dev.minios.pdaiv1.data.mocks.mockFalAiEndpoints +import dev.minios.pdaiv1.domain.datasource.FalAiEndpointDataSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class FalAiEndpointRepositoryImplTest { + + private val stubException = Throwable("Something went wrong.") + private val stubBuiltInDataSource = mockk() + private val stubRemoteDataSource = mockk() + private val stubLocalDataSource = mockk() + private val stubPreferenceManager = mockk(relaxed = true) + + private val repository = FalAiEndpointRepositoryImpl( + builtInDataSource = stubBuiltInDataSource, + remoteDataSource = stubRemoteDataSource, + localDataSource = stubLocalDataSource, + preferenceManager = stubPreferenceManager, + ) + + @Test + fun `given attempt to observe all endpoints, both sources return data, expected combined list`() { + val customEndpoint = mockFalAiEndpoint.copy(id = "custom-endpoint", isCustom = true) + + every { + stubBuiltInDataSource.getAll() + } returns Single.just(mockFalAiEndpoints) + + every { + stubLocalDataSource.observeAll() + } returns Observable.just(listOf(customEndpoint)) + + repository + .observeAll() + .test() + .assertNoErrors() + .assertValueAt(1) { it.size == 2 && it.containsAll(mockFalAiEndpoints + customEndpoint) } + .dispose() + } + + @Test + fun `given attempt to get all endpoints, both sources return data, expected combined list`() { + val customEndpoint = mockFalAiEndpoint.copy(id = "custom-endpoint", isCustom = true) + + every { + stubBuiltInDataSource.getAll() + } returns Single.just(mockFalAiEndpoints) + + every { + stubLocalDataSource.getAll() + } returns Single.just(listOf(customEndpoint)) + + repository + .getAll() + .test() + .assertNoErrors() + .assertValue { it.size == 2 && it.containsAll(mockFalAiEndpoints + customEndpoint) } + .await() + .assertComplete() + } + + @Test + fun `given attempt to get all endpoints, local source fails, expected built-in endpoints only`() { + every { + stubBuiltInDataSource.getAll() + } returns Single.just(mockFalAiEndpoints) + + every { + stubLocalDataSource.getAll() + } returns Single.error(stubException) + + repository + .getAll() + .test() + .assertNoErrors() + .assertValue(mockFalAiEndpoints) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get endpoint by id, endpoint exists, expected valid endpoint`() { + every { + stubBuiltInDataSource.getAll() + } returns Single.just(mockFalAiEndpoints) + + every { + stubLocalDataSource.getAll() + } returns Single.just(emptyList()) + + repository + .getById(mockFalAiEndpoint.id) + .test() + .assertNoErrors() + .assertValue(mockFalAiEndpoint) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get endpoint by id, endpoint not found, expected error`() { + every { + stubBuiltInDataSource.getAll() + } returns Single.just(mockFalAiEndpoints) + + every { + stubLocalDataSource.getAll() + } returns Single.just(emptyList()) + + repository + .getById("non-existent-id") + .test() + .assertError { it is NoSuchElementException } + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to get selected endpoint, preference has valid id, expected valid endpoint`() { + every { + stubPreferenceManager.falAiSelectedEndpointId + } returns mockFalAiEndpoint.id + + every { + stubBuiltInDataSource.getAll() + } returns Single.just(mockFalAiEndpoints) + + every { + stubLocalDataSource.getAll() + } returns Single.just(emptyList()) + + repository + .getSelected() + .test() + .assertNoErrors() + .assertValue(mockFalAiEndpoint) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get selected endpoint, preference is blank, expected first built-in endpoint`() { + every { + stubPreferenceManager.falAiSelectedEndpointId + } returns "" + + every { + stubBuiltInDataSource.getAll() + } returns Single.just(mockFalAiEndpoints) + + repository + .getSelected() + .test() + .assertNoErrors() + .assertValue(mockFalAiEndpoints.first()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to set selected endpoint, expected preference updated`() { + every { + stubPreferenceManager.falAiSelectedEndpointId = any() + } returns Unit + + repository + .setSelected("new-endpoint-id") + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { stubPreferenceManager.falAiSelectedEndpointId = "new-endpoint-id" } + } + + @Test + fun `given attempt to import from url, remote returns endpoint, expected endpoint saved and returned`() { + val importedEndpoint = mockFalAiEndpoint.copy(id = "imported", isCustom = true) + + every { + stubRemoteDataSource.fetchFromUrl(any()) + } returns Single.just(importedEndpoint) + + every { + stubLocalDataSource.save(any()) + } returns Completable.complete() + + repository + .importFromUrl("https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=test") + .test() + .assertNoErrors() + .assertValue(importedEndpoint) + .await() + .assertComplete() + + verify { stubLocalDataSource.save(importedEndpoint) } + } + + @Test + fun `given attempt to import from url, remote fails, expected error`() { + every { + stubRemoteDataSource.fetchFromUrl(any()) + } returns Single.error(stubException) + + repository + .importFromUrl("https://fal.ai/api/invalid") + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to delete endpoint, expected delete called on local source`() { + every { + stubLocalDataSource.delete(any()) + } returns Completable.complete() + + repository + .delete("endpoint-to-delete") + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { stubLocalDataSource.delete("endpoint-to-delete") } + } + + @Test + fun `given attempt to delete endpoint, local source fails, expected error`() { + every { + stubLocalDataSource.delete(any()) + } returns Completable.error(stubException) + + repository + .delete("endpoint-to-delete") + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/data/src/test/java/dev/minios/pdaiv1/data/repository/FalAiGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/FalAiGenerationRepositoryImplTest.kt new file mode 100644 index 000000000..705c6d58a --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/FalAiGenerationRepositoryImplTest.kt @@ -0,0 +1,178 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResults +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.FalAiGenerationDataSource +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiEndpointCategory +import dev.minios.pdaiv1.domain.entity.FalAiEndpointSchema +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class FalAiGenerationRepositoryImplTest { + + private val stubException = Throwable("Something went wrong.") + private val stubRemoteDataSource = mockk() + private val stubEndpointRepository = mockk() + + private val stubEndpoint = FalAiEndpoint( + id = "fal-ai/flux/schnell", + endpointId = "fal-ai/flux/schnell", + title = "FLUX.1 [schnell]", + description = "Fast generation", + category = FalAiEndpointCategory.TEXT_TO_IMAGE, + group = "FLUX", + thumbnailUrl = "", + playgroundUrl = "", + documentationUrl = "", + isCustom = false, + schema = FalAiEndpointSchema( + baseUrl = "https://queue.fal.run", + submissionPath = "/fal-ai/flux/schnell", + inputProperties = emptyList(), + requiredProperties = emptyList(), + propertyOrder = emptyList(), + ), + ) + + private val repository = FalAiGenerationRepositoryImpl( + remoteDataSource = stubRemoteDataSource, + endpointRepository = stubEndpointRepository, + ) + + @Test + fun `given attempt to validate api key, remote returns true, expected true value`() { + every { + stubRemoteDataSource.validateApiKey() + } returns Single.just(true) + + repository + .validateApiKey() + .test() + .assertNoErrors() + .assertValue(true) + .await() + .assertComplete() + } + + @Test + fun `given attempt to validate api key, remote returns false, expected false value`() { + every { + stubRemoteDataSource.validateApiKey() + } returns Single.just(false) + + repository + .validateApiKey() + .test() + .assertNoErrors() + .assertValue(false) + .await() + .assertComplete() + } + + @Test + fun `given attempt to validate api key, remote throws exception, expected error value`() { + every { + stubRemoteDataSource.validateApiKey() + } returns Single.error(stubException) + + repository + .validateApiKey() + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to generate from text, endpoint selected and remote returns data, expected valid result`() { + every { + stubEndpointRepository.getSelected() + } returns Single.just(stubEndpoint) + + every { + stubRemoteDataSource.textToImage(any(), any()) + } returns Single.just(mockAiGenerationResult) + + repository + .generateFromText(mockTextToImagePayload) + .test() + .assertNoErrors() + .assertValue(mockAiGenerationResult) + .await() + .assertComplete() + } + + @Test + fun `given attempt to generate from text, endpoint selection fails, expected error value`() { + every { + stubEndpointRepository.getSelected() + } returns Single.error(stubException) + + repository + .generateFromText(mockTextToImagePayload) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to generate from text, remote generation fails, expected error value`() { + every { + stubEndpointRepository.getSelected() + } returns Single.just(stubEndpoint) + + every { + stubRemoteDataSource.textToImage(any(), any()) + } returns Single.error(stubException) + + repository + .generateFromText(mockTextToImagePayload) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to generate dynamic, remote returns data, expected valid results list`() { + val stubParameters = mapOf("prompt" to "test") + + every { + stubRemoteDataSource.generateDynamic(any(), any()) + } returns Single.just(mockAiGenerationResults) + + repository + .generateDynamic(stubEndpoint, stubParameters) + .test() + .assertNoErrors() + .assertValue(mockAiGenerationResults) + .await() + .assertComplete() + } + + @Test + fun `given attempt to generate dynamic, remote throws exception, expected error value`() { + val stubParameters = mapOf("prompt" to "test") + + every { + stubRemoteDataSource.generateDynamic(any(), any()) + } returns Single.error(stubException) + + repository + .generateDynamic(stubEndpoint, stubParameters) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } +} diff --git a/data/src/test/java/dev/minios/pdaiv1/data/repository/ForgeModulesRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/ForgeModulesRepositoryImplTest.kt new file mode 100644 index 000000000..af557dc40 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/ForgeModulesRepositoryImplTest.kt @@ -0,0 +1,100 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.data.provider.ServerUrlProvider +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.model.ForgeModuleRaw +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.core.Single +import org.junit.Before +import org.junit.Test + +class ForgeModulesRepositoryImplTest { + + private val stubException = Throwable("Something went wrong.") + private val stubServerUrlProvider = mockk() + private val stubApi = mockk() + + private val stubModulesRaw = listOf( + ForgeModuleRaw(modelName = "ADetailer", filename = "extensions/adetailer"), + ForgeModuleRaw(modelName = "ControlNet", filename = "extensions/controlnet"), + ) + + private val expectedModules = listOf( + ForgeModule(name = "ADetailer", path = "extensions/adetailer"), + ForgeModule(name = "ControlNet", path = "extensions/controlnet"), + ) + + private val repository = ForgeModulesRepositoryImpl( + serverUrlProvider = stubServerUrlProvider, + api = stubApi, + ) + + @Before + fun initialize() { + every { + stubServerUrlProvider(any()) + } returns Single.just("http://192.168.0.1:7860/sdapi/v1/extensions") + } + + @Test + fun `given attempt to fetch modules, api returns data, expected valid modules list value`() { + every { + stubApi.fetchForgeModules(any()) + } returns Single.just(stubModulesRaw) + + repository + .fetchModules() + .test() + .assertNoErrors() + .assertValue(expectedModules) + .await() + .assertComplete() + } + + @Test + fun `given attempt to fetch modules, api returns empty list, expected empty list value`() { + every { + stubApi.fetchForgeModules(any()) + } returns Single.just(emptyList()) + + repository + .fetchModules() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to fetch modules, api throws exception, expected empty list value due to onErrorReturn`() { + every { + stubApi.fetchForgeModules(any()) + } returns Single.error(stubException) + + repository + .fetchModules() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to fetch modules, url provider fails, expected empty list value due to onErrorReturn`() { + every { + stubServerUrlProvider(any()) + } returns Single.error(stubException) + + repository + .fetchModules() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } +} diff --git a/data/src/test/java/dev/minios/pdaiv1/data/repository/GenerationResultRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/GenerationResultRepositoryImplTest.kt new file mode 100644 index 000000000..ffce27585 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/GenerationResultRepositoryImplTest.kt @@ -0,0 +1,550 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResults +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class GenerationResultRepositoryImplTest { + + private val stubException = Throwable("Something went wrong.") + private val stubPreferenceManager = mockk() + private val stubMediaStoreGateway = mockk() + private val stubBase64ToBitmapConverter = mockk() + private val stubLocalDataSource = mockk() + private val stubMediaFileManager = mockk() + + private val repository = GenerationResultRepositoryImpl( + preferenceManager = stubPreferenceManager, + mediaStoreGateway = stubMediaStoreGateway, + base64ToBitmapConverter = stubBase64ToBitmapConverter, + localDataSource = stubLocalDataSource, + mediaFileManager = stubMediaFileManager, + ) + + @Test + fun `given attempt to get all, local returns data, expected valid domain model list value`() { + every { + stubLocalDataSource.queryAll() + } returns Single.just(mockAiGenerationResults) + + repository + .getAll() + .test() + .assertNoErrors() + .assertValue(mockAiGenerationResults) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get all, local returns empty data, expected empty domain model list value`() { + every { + stubLocalDataSource.queryAll() + } returns Single.just(emptyList()) + + repository + .getAll() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get all, local throws exception, expected error value`() { + every { + stubLocalDataSource.queryAll() + } returns Single.error(stubException) + + repository + .getAll() + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to get page, local returns data, expected valid domain model list value`() { + every { + stubLocalDataSource.queryPage(any(), any()) + } returns Single.just(mockAiGenerationResults) + + repository + .getPage(20, 0) + .test() + .assertNoErrors() + .assertValue(mockAiGenerationResults) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get page, local returns empty data, expected empty domain model list value`() { + every { + stubLocalDataSource.queryPage(any(), any()) + } returns Single.just(emptyList()) + + repository + .getPage(20, 0) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get page, local throws exception, expected error value`() { + every { + stubLocalDataSource.queryPage(any(), any()) + } returns Single.error(stubException) + + repository + .getPage(20, 0) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to get media store info, gateway returned data, expected valid media store info value`() { + every { + stubMediaStoreGateway.getInfo() + } returns MediaStoreInfo() + + repository + .getMediaStoreInfo() + .test() + .assertNoErrors() + .assertValue(MediaStoreInfo()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get media store info, gateway throws exception, expected error value`() { + every { + stubMediaStoreGateway.getInfo() + } throws stubException + + repository + .getMediaStoreInfo() + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to get by id, local returns data, expected valid domain model value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) + + repository + .getById(5598L) + .test() + .assertNoErrors() + .assertValue(mockAiGenerationResult) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get by id, local throws exception, expected error value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.error(stubException) + + repository + .getById(5598L) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to delete by id list, local delete success, expected complete value`() { + every { + stubLocalDataSource.queryByIdList(any()) + } returns Single.just(mockAiGenerationResults) + + every { + stubLocalDataSource.deleteByIdList(any()) + } returns Completable.complete() + + repository + .deleteByIdList(listOf(5598L, 151297L)) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to delete by id list, local delete fails, expected error value`() { + every { + stubLocalDataSource.queryByIdList(any()) + } returns Single.just(mockAiGenerationResults) + + every { + stubLocalDataSource.deleteByIdList(any()) + } returns Completable.error(stubException) + + repository + .deleteByIdList(listOf(5598L, 151297L)) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to delete by id, local delete success, expected complete value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) + + every { + stubLocalDataSource.deleteById(any()) + } returns Completable.complete() + + repository + .deleteById(5598L) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to delete by id, local delete fails, expected error value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) + + every { + stubLocalDataSource.deleteById(any()) + } returns Completable.error(stubException) + + repository + .deleteById(5598L) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to delete all, local delete success, expected complete value`() { + every { + stubLocalDataSource.queryAll() + } returns Single.just(mockAiGenerationResults) + + every { + stubLocalDataSource.deleteAll() + } returns Completable.complete() + + repository + .deleteAll() + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to delete all, local delete fails, expected complete value`() { + every { + stubLocalDataSource.queryAll() + } returns Single.just(mockAiGenerationResults) + + every { + stubLocalDataSource.deleteAll() + } returns Completable.error(stubException) + + repository + .deleteAll() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to insert data, local insert success, expected id of inserted model value`() { + every { + stubPreferenceManager.saveToMediaStore + } returns false + + every { + stubMediaFileManager.isFilePath(any()) + } returns false + + every { + stubMediaFileManager.isVideoUrl(any()) + } returns false + + every { + stubMediaFileManager.migrateBase64ToFile(any(), any()) + } returns "/path/to/file.png" + + every { + stubLocalDataSource.insert(any()) + } returns Single.just(mockAiGenerationResult.id) + + repository + .insert(mockAiGenerationResult) + .test() + .assertNoErrors() + .assertValue(5598L) + .await() + .assertComplete() + } + + @Test + fun `given attempt to insert data, local insert fails, expected error value`() { + every { + stubPreferenceManager.saveToMediaStore + } returns false + + every { + stubMediaFileManager.isFilePath(any()) + } returns false + + every { + stubMediaFileManager.isVideoUrl(any()) + } returns false + + every { + stubMediaFileManager.migrateBase64ToFile(any(), any()) + } returns "/path/to/file.png" + + every { + stubLocalDataSource.insert(any()) + } returns Single.error(stubException) + + repository + .insert(mockAiGenerationResult) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to toggle image visibility, process succeeds, expected boolean value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) + + every { + stubLocalDataSource.insert(any()) + } returns Single.just(5598L) + + repository + .toggleVisibility(5598L) + .test() + .await() + .assertComplete() + } + + @Test + fun `given attempt to toggle image visibility, error occurs, expected boolean value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) + + every { + stubLocalDataSource.insert(any()) + } returns Single.error(stubException) + + repository + .toggleVisibility(5598L) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to toggle like, local returns data, expected boolean value`() { + val toggledResult = mockAiGenerationResult.copy(liked = true) + + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) andThen Single.just(toggledResult) + + every { + stubLocalDataSource.insert(any()) + } returns Single.just(5598L) + + repository + .toggleLike(5598L) + .test() + .assertNoErrors() + .assertValue(true) + .await() + .assertComplete() + } + + @Test + fun `given attempt to like by ids, local success, expected complete value`() { + every { + stubLocalDataSource.likeByIds(any()) + } returns Completable.complete() + + repository + .likeByIds(listOf(5598L, 151297L)) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to like by ids, local fails, expected error value`() { + every { + stubLocalDataSource.likeByIds(any()) + } returns Completable.error(stubException) + + repository + .likeByIds(listOf(5598L, 151297L)) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to hide by ids, local success, expected complete value`() { + every { + stubLocalDataSource.hideByIds(any()) + } returns Completable.complete() + + repository + .hideByIds(listOf(5598L, 151297L)) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to hide by ids, local fails, expected error value`() { + every { + stubLocalDataSource.hideByIds(any()) + } returns Completable.error(stubException) + + repository + .hideByIds(listOf(5598L, 151297L)) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to unlike by ids, local success, expected complete value`() { + every { + stubLocalDataSource.unlikeByIds(any()) + } returns Completable.complete() + + repository + .unlikeByIds(listOf(5598L, 151297L)) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to unlike by ids, local fails, expected error value`() { + every { + stubLocalDataSource.unlikeByIds(any()) + } returns Completable.error(stubException) + + repository + .unlikeByIds(listOf(5598L, 151297L)) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to unhide by ids, local success, expected complete value`() { + every { + stubLocalDataSource.unhideByIds(any()) + } returns Completable.complete() + + repository + .unhideByIds(listOf(5598L, 151297L)) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to unhide by ids, local fails, expected error value`() { + every { + stubLocalDataSource.unhideByIds(any()) + } returns Completable.error(stubException) + + repository + .unhideByIds(listOf(5598L, 151297L)) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to delete all unliked, local success, expected complete value`() { + every { + stubLocalDataSource.deleteAllUnliked() + } returns Completable.complete() + + repository + .deleteAllUnliked() + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to delete all unliked, local fails, expected error value`() { + every { + stubLocalDataSource.deleteAllUnliked() + } returns Completable.error(stubException) + + repository + .deleteAllUnliked() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/HordeGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/HordeGenerationRepositoryImplTest.kt similarity index 86% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/HordeGenerationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/HordeGenerationRepositoryImplTest.kt index 7254c0c84..db0b499e8 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/HordeGenerationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/HordeGenerationRepositoryImplTest.kt @@ -1,15 +1,17 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.HordeGenerationDataSource -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.HordeGenerationDataSource +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.BackpressureStrategy @@ -31,15 +33,19 @@ class HordeGenerationRepositoryImplTest { private val stubRemoteDataSource = mockk() private val stubStatusSource = mockk() private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() private val repository = HordeGenerationRepositoryImpl( mediaStoreGateway = stubMediaStoreGateway, base64ToBitmapConverter = stubBase64ToBitmapConverter, localDataSource = stubLocalDataSource, preferenceManager = stubPreferenceManager, + backgroundWorkObserver = stubBackgroundWorkObserver, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, remoteDataSource = stubRemoteDataSource, statusSource = stubStatusSource, - backgroundWorkObserver = stubBackgroundWorkObserver, ) @Before diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/HuggingFaceGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/HuggingFaceGenerationRepositoryImplTest.kt similarity index 82% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/HuggingFaceGenerationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/HuggingFaceGenerationRepositoryImplTest.kt index e3fca1960..a041eeeb1 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/HuggingFaceGenerationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/HuggingFaceGenerationRepositoryImplTest.kt @@ -1,14 +1,16 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceGenerationDataSource -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.HuggingFaceGenerationDataSource +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single @@ -24,14 +26,18 @@ class HuggingFaceGenerationRepositoryImplTest { private val stubPreferenceManager = mockk() private val stubRemoteDataSource = mockk() private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() private val repository = HuggingFaceGenerationRepositoryImpl( mediaStoreGateway = stubMediaStoreGateway, base64ToBitmapConverter = stubBase64ToBitmapConverter, localDataSource = stubLocalDataSource, + backgroundWorkObserver = stubBackgroundWorkObserver, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, preferenceManager = stubPreferenceManager, remoteDataSource = stubRemoteDataSource, - backgroundWorkObserver = stubBackgroundWorkObserver, ) @Before diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/HuggingFaceModelsRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/HuggingFaceModelsRepositoryImplTest.kt similarity index 96% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/HuggingFaceModelsRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/HuggingFaceModelsRepositoryImplTest.kt index e6cb8716c..bc282bb50 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/HuggingFaceModelsRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/HuggingFaceModelsRepositoryImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockHuggingFaceModels -import com.shifthackz.aisdv1.domain.datasource.HuggingFaceModelsDataSource +import dev.minios.pdaiv1.data.mocks.mockHuggingFaceModels +import dev.minios.pdaiv1.domain.datasource.HuggingFaceModelsDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt similarity index 84% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt index 126edac1d..6852c82f4 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt @@ -1,20 +1,22 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.data.mocks.mockLocalAiModel -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockLocalAiModel +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.diffusion.LocalDiffusion +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable @@ -41,6 +43,8 @@ class LocalDiffusionGenerationRepositoryImplTest { private val stubLocalDiffusion = mockk() private val stubDownloadableLocalDataSource = mockk() private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() private val stubSchedulersProvider = object : SchedulersProvider { override val io: Scheduler = Schedulers.trampoline() @@ -53,12 +57,14 @@ class LocalDiffusionGenerationRepositoryImplTest { mediaStoreGateway = stubMediaStoreGateway, base64ToBitmapConverter = stubBase64ToBitmapConverter, localDataSource = stubLocalDataSource, + backgroundWorkObserver = stubBackgroundWorkObserver, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, preferenceManager = stubPreferenceManager, localDiffusion = stubLocalDiffusion, downloadableLocalDataSource = stubDownloadableLocalDataSource, bitmapToBase64Converter = stubBitmapToBase64Converter, schedulersProvider = stubSchedulersProvider, - backgroundWorkObserver = stubBackgroundWorkObserver, ) @Before diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LorasRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/LorasRepositoryImplTest.kt similarity index 96% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/LorasRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/LorasRepositoryImplTest.kt index 3bff4b372..e9dca1e2f 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LorasRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/LorasRepositoryImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionLoras -import com.shifthackz.aisdv1.domain.datasource.LorasDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionLoras +import dev.minios.pdaiv1.domain.datasource.LorasDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/dev/minios/pdaiv1/data/repository/MediaPipeGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/MediaPipeGenerationRepositoryImplTest.kt new file mode 100644 index 000000000..652fd51bb --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/MediaPipeGenerationRepositoryImplTest.kt @@ -0,0 +1,109 @@ +package dev.minios.pdaiv1.data.repository + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.mediapipe.MediaPipe +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import org.junit.Before +import org.junit.Test +import java.util.concurrent.Executor +import java.util.concurrent.Executors + +class MediaPipeGenerationRepositoryImplTest { + + private val stubBitmap = mockk() + private val stubException = Throwable("Something went wrong.") + private val stubMediaStoreGateway = mockk() + private val stubBase64ToBitmapConverter = mockk() + private val stubBitmapToBase64Converter = mockk() + private val stubLocalDataSource = mockk() + private val stubPreferenceManager = mockk() + private val stubMediaPipe = mockk() + private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() + + private val stubSchedulersProvider = object : SchedulersProvider { + override val io: Scheduler = Schedulers.trampoline() + override val ui: Scheduler = Schedulers.trampoline() + override val computation: Scheduler = Schedulers.trampoline() + override val singleThread: Executor = Executors.newSingleThreadExecutor() + } + + private val repository = MediaPipeGenerationRepositoryImpl( + mediaStoreGateway = stubMediaStoreGateway, + base64ToBitmapConverter = stubBase64ToBitmapConverter, + localDataSource = stubLocalDataSource, + backgroundWorkObserver = stubBackgroundWorkObserver, + mediaFileManager = stubMediaFileManager, + preferenceManager = stubPreferenceManager, + blurHashEncoder = stubBlurHashEncoder, + schedulersProvider = stubSchedulersProvider, + mediaPipe = stubMediaPipe, + bitmapToBase64Converter = stubBitmapToBase64Converter, + ) + + @Before + fun initialize() { + every { + stubBackgroundWorkObserver.hasActiveTasks() + } returns false + + every { + stubPreferenceManager.autoSaveAiResults + } returns false + } + + @Test + fun `given MediaPipe process fails, expected error value`() { + val payload = mockk(relaxed = true) + + every { + stubMediaPipe.process(any()) + } returns Single.error(stubException) + + repository + .generateFromText(payload) + .test() + .await() + .assertError(stubException) + .assertNoValues() + .assertNotComplete() + + verify { stubMediaPipe.process(any()) } + } + + @Test + fun `given MediaPipe process success but bitmap conversion fails, expected error value`() { + val payload = mockk(relaxed = true) + + every { + stubMediaPipe.process(any()) + } returns Single.just(stubBitmap) + + every { + stubBitmapToBase64Converter(any()) + } returns Single.error(stubException) + + repository + .generateFromText(payload) + .test() + .await() + .assertError(stubException) + .assertNoValues() + .assertNotComplete() + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/OpenAiGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/OpenAiGenerationRepositoryImplTest.kt similarity index 79% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/OpenAiGenerationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/OpenAiGenerationRepositoryImplTest.kt index 2e014b7d9..b6e6072b4 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/OpenAiGenerationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/OpenAiGenerationRepositoryImplTest.kt @@ -1,13 +1,15 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.OpenAiGenerationDataSource -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.OpenAiGenerationDataSource +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single @@ -23,14 +25,18 @@ class OpenAiGenerationRepositoryImplTest { private val stubPreferenceManager = mockk() private val stubRemoteDataSource = mockk() private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() private val repository = OpenAiGenerationRepositoryImpl( mediaStoreGateway = stubMediaStoreGateway, base64ToBitmapConverter = stubBase64ToBitmapConverter, localDataSource = stubLocalDataSource, preferenceManager = stubPreferenceManager, - remoteDataSource = stubRemoteDataSource, backgroundWorkObserver = stubBackgroundWorkObserver, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, + remoteDataSource = stubRemoteDataSource, ) @Before diff --git a/data/src/test/java/dev/minios/pdaiv1/data/repository/QnnGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/QnnGenerationRepositoryImplTest.kt new file mode 100644 index 000000000..c51591bde --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/QnnGenerationRepositoryImplTest.kt @@ -0,0 +1,216 @@ +package dev.minios.pdaiv1.data.repository + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.qnn.LocalQnn +import dev.minios.pdaiv1.domain.feature.qnn.QnnGenerationResult +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import io.reactivex.rxjava3.subjects.BehaviorSubject +import org.junit.Before +import org.junit.Test + +class QnnGenerationRepositoryImplTest { + + private val stubBitmap = mockk() + private val stubQnnGenerationResult = QnnGenerationResult( + bitmap = stubBitmap, + seed = 12345L, + width = 512, + height = 512, + ) + private val stubException = Throwable("Something went wrong.") + private val stubStatus = BehaviorSubject.create() + private val stubMediaStoreGateway = mockk() + private val stubBase64ToBitmapConverter = mockk() + private val stubBitmapToBase64Converter = mockk() + private val stubLocalDataSource = mockk() + private val stubPreferenceManager = mockk() + private val stubLocalQnn = mockk() + private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubSchedulersProvider = mockk() + private val stubBlurHashEncoder = mockk() + + private val repository = QnnGenerationRepositoryImpl( + mediaStoreGateway = stubMediaStoreGateway, + base64ToBitmapConverter = stubBase64ToBitmapConverter, + localDataSource = stubLocalDataSource, + backgroundWorkObserver = stubBackgroundWorkObserver, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, + preferenceManager = stubPreferenceManager, + localQnn = stubLocalQnn, + bitmapToBase64Converter = stubBitmapToBase64Converter, + schedulersProvider = stubSchedulersProvider, + ) + + @Before + fun initialize() { + every { + stubBackgroundWorkObserver.hasActiveTasks() + } returns false + + every { + stubLocalQnn.observeStatus() + } returns stubStatus + + every { + stubPreferenceManager.autoSaveAiResults + } returns false + + every { + stubSchedulersProvider.io + } returns Schedulers.trampoline() + } + + @Test + fun `given attempt to observe status, local emits two values, expected same values with same order`() { + val stubObserver = repository.observeStatus().test() + + stubStatus.onNext(LocalDiffusionStatus(1, 2)) + + stubObserver + .assertNoErrors() + .assertValueAt(0, LocalDiffusionStatus(1, 2)) + + stubStatus.onNext(LocalDiffusionStatus(2, 2)) + + stubObserver + .assertNoErrors() + .assertValueAt(1, LocalDiffusionStatus(2, 2)) + } + + @Test + fun `given attempt to observe status, local throws exception, expected error value`() { + every { + stubLocalQnn.observeStatus() + } returns Observable.error(stubException) + + repository + .observeStatus() + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to interrupt generation, local completes, expected complete value`() { + every { + stubLocalQnn.interrupt() + } returns Completable.complete() + + repository + .interruptGeneration() + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to interrupt generation, local throws exception, expected error value`() { + every { + stubLocalQnn.interrupt() + } returns Completable.error(stubException) + + repository + .interruptGeneration() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given attempt to generate from text, local returns bitmap, expected ai generation result`() { + val mockBase64Output = BitmapToBase64Converter.Output("base64image") + + every { + stubLocalQnn.processTextToImage(any()) + } returns Single.just(stubQnnGenerationResult) + + every { + stubBitmapToBase64Converter.invoke(any()) + } returns Single.just(mockBase64Output) + + every { + stubLocalDataSource.insert(any()) + } returns Single.just(1L) + + repository + .generateFromText(mockTextToImagePayload) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to generate from text, local throws exception, expected error value`() { + every { + stubLocalQnn.processTextToImage(any()) + } returns Single.error(stubException) + + repository + .generateFromText(mockTextToImagePayload) + .test() + .assertError(stubException) + .assertNoValues() + } + + @Test + fun `given attempt to generate from image, local returns bitmap, expected ai generation result`() { + val mockBase64Output = BitmapToBase64Converter.Output("base64image") + + every { + stubLocalQnn.processImageToImage(any()) + } returns Single.just(stubQnnGenerationResult) + + every { + stubBitmapToBase64Converter.invoke(any()) + } returns Single.just(mockBase64Output) + + every { + stubLocalDataSource.insert(any()) + } returns Single.just(1L) + + repository + .generateFromImage(mockImageToImagePayload) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to generate from image, local throws exception, expected error value`() { + every { + stubLocalQnn.processImageToImage(any()) + } returns Single.error(stubException) + + repository + .generateFromImage(mockImageToImagePayload) + .test() + .assertError(stubException) + .assertNoValues() + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/RandomImageRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/RandomImageRepositoryImplTest.kt similarity index 91% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/RandomImageRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/RandomImageRepositoryImplTest.kt index 4bbef387d..4ec4b8548 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/RandomImageRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/RandomImageRepositoryImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository import android.graphics.Bitmap -import com.shifthackz.aisdv1.domain.datasource.RandomImageDataSource +import dev.minios.pdaiv1.domain.datasource.RandomImageDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/dev/minios/pdaiv1/data/repository/ReportRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/ReportRepositoryImplTest.kt new file mode 100644 index 000000000..49f0502e4 --- /dev/null +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/ReportRepositoryImplTest.kt @@ -0,0 +1,153 @@ +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.domain.datasource.ReportDataSource +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Completable +import org.junit.Test + +class ReportRepositoryImplTest { + + private val stubException = Throwable("Something went wrong.") + private val stubRemoteDataSource = mockk() + private val stubPreferenceManager = mockk() + + private val repository = ReportRepositoryImpl( + rds = stubRemoteDataSource, + preferenceManager = stubPreferenceManager, + ) + + @Test + fun `given attempt to send report with HuggingFace source, expected remote called with correct model`() { + val text = "Bug report" + val reason = ReportReason.Other + val image = "base64image" + val huggingFaceModel = "stabilityai/sdxl-turbo" + + every { stubPreferenceManager.source } returns ServerSource.HUGGING_FACE + every { stubPreferenceManager.huggingFaceModel } returns huggingFaceModel + every { + stubRemoteDataSource.send(text, reason, image, ServerSource.HUGGING_FACE.toString(), huggingFaceModel) + } returns Completable.complete() + + repository + .send(text, reason, image) + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { + stubRemoteDataSource.send(text, reason, image, ServerSource.HUGGING_FACE.toString(), huggingFaceModel) + } + } + + @Test + fun `given attempt to send report with StabilityAI source, expected remote called with correct engine id`() { + val text = "Feature request" + val reason = ReportReason.InappropriateContent + val image = "" + val engineId = "stable-diffusion-xl-1024-v1-0" + + every { stubPreferenceManager.source } returns ServerSource.STABILITY_AI + every { stubPreferenceManager.stabilityAiEngineId } returns engineId + every { + stubRemoteDataSource.send(text, reason, image, ServerSource.STABILITY_AI.toString(), engineId) + } returns Completable.complete() + + repository + .send(text, reason, image) + .test() + .assertNoErrors() + .await() + .assertComplete() + + verify { + stubRemoteDataSource.send(text, reason, image, ServerSource.STABILITY_AI.toString(), engineId) + } + } + + @Test + fun `given attempt to send report with ONNX source, expected remote called with correct model id`() { + val text = "Crash report" + val reason = ReportReason.Other + val image = "base64" + val modelId = "onnx-model-id" + + every { stubPreferenceManager.source } returns ServerSource.LOCAL_MICROSOFT_ONNX + every { stubPreferenceManager.localOnnxModelId } returns modelId + every { + stubRemoteDataSource.send(text, reason, image, ServerSource.LOCAL_MICROSOFT_ONNX.toString(), modelId) + } returns Completable.complete() + + repository + .send(text, reason, image) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to send report with MediaPipe source, expected remote called with correct model id`() { + val text = "MediaPipe issue" + val reason = ReportReason.Other + val image = "" + val modelId = "mediapipe-model-id" + + every { stubPreferenceManager.source } returns ServerSource.LOCAL_GOOGLE_MEDIA_PIPE + every { stubPreferenceManager.localMediaPipeModelId } returns modelId + every { + stubRemoteDataSource.send(text, reason, image, ServerSource.LOCAL_GOOGLE_MEDIA_PIPE.toString(), modelId) + } returns Completable.complete() + + repository + .send(text, reason, image) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to send report with Automatic1111 source, expected remote called with empty model`() { + val text = "A1111 issue" + val reason = ReportReason.Other + val image = "" + + every { stubPreferenceManager.source } returns ServerSource.AUTOMATIC1111 + every { + stubRemoteDataSource.send(text, reason, image, ServerSource.AUTOMATIC1111.toString(), "") + } returns Completable.complete() + + repository + .send(text, reason, image) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given attempt to send report, remote throws exception, expected error value`() { + val text = "Bug report" + val reason = ReportReason.Other + val image = "" + + every { stubPreferenceManager.source } returns ServerSource.AUTOMATIC1111 + every { + stubRemoteDataSource.send(any(), any(), any(), any(), any()) + } returns Completable.error(stubException) + + repository + .send(text, reason, image) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/ServerConfigurationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/ServerConfigurationRepositoryImplTest.kt similarity index 96% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/ServerConfigurationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/ServerConfigurationRepositoryImplTest.kt index 4667890c8..418e26fe2 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/ServerConfigurationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/ServerConfigurationRepositoryImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockServerConfiguration -import com.shifthackz.aisdv1.domain.datasource.ServerConfigurationDataSource +import dev.minios.pdaiv1.data.mocks.mockServerConfiguration +import dev.minios.pdaiv1.domain.datasource.ServerConfigurationDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiCreditsRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiCreditsRepositoryImplTest.kt similarity index 98% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiCreditsRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiCreditsRepositoryImplTest.kt index 7ef7f17a9..9934cd1ee 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiCreditsRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiCreditsRepositoryImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.BackpressureStrategy diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiEnginesRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiEnginesRepositoryImplTest.kt similarity index 90% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiEnginesRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiEnginesRepositoryImplTest.kt index 24a1b3194..00b5d4f37 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiEnginesRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiEnginesRepositoryImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockStabilityAiEngines -import com.shifthackz.aisdv1.domain.datasource.StabilityAiEnginesDataSource +import dev.minios.pdaiv1.data.mocks.mockStabilityAiEngines +import dev.minios.pdaiv1.domain.datasource.StabilityAiEnginesDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiGenerationRepositoryImplTest.kt similarity index 83% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiGenerationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiGenerationRepositoryImplTest.kt index e80a07c31..82ac592c0 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StabilityAiGenerationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/StabilityAiGenerationRepositoryImplTest.kt @@ -1,16 +1,18 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiCreditsDataSource -import com.shifthackz.aisdv1.domain.datasource.StabilityAiGenerationDataSource -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiCreditsDataSource +import dev.minios.pdaiv1.domain.datasource.StabilityAiGenerationDataSource +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable @@ -30,16 +32,20 @@ class StabilityAiGenerationRepositoryImplTest { private val stubCreditsRds = mockk() private val stubCreditsLds = mockk() private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() private val repository = StabilityAiGenerationRepositoryImpl( mediaStoreGateway = stubMediaStoreGateway, + backgroundWorkObserver = stubBackgroundWorkObserver, base64ToBitmapConverter = stubBase64ToBitmapConverter, localDataSource = stubLocalDataSource, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, preferenceManager = stubPreferenceManager, generationRds = stubRemoteDataSource, creditsRds = stubCreditsRds, creditsLds = stubCreditsLds, - backgroundWorkObserver = stubBackgroundWorkObserver, ) @Before diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionGenerationRepositoryImplTest.kt similarity index 88% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionGenerationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionGenerationRepositoryImplTest.kt index 7bd941ec0..3db21a62b 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionGenerationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionGenerationRepositoryImplTest.kt @@ -1,16 +1,18 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionGenerationDataSource -import com.shifthackz.aisdv1.domain.demo.ImageToImageDemo -import com.shifthackz.aisdv1.domain.demo.TextToImageDemo -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.StableDiffusionGenerationDataSource +import dev.minios.pdaiv1.domain.demo.ImageToImageDemo +import dev.minios.pdaiv1.domain.demo.TextToImageDemo +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable @@ -29,16 +31,20 @@ class StableDiffusionGenerationRepositoryImplTest { private val stubTextToImageDemo = mockk() private val stubImageToImageDemo = mockk() private val stubBackgroundWorkObserver = mockk() + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() private val repository = StableDiffusionGenerationRepositoryImpl( mediaStoreGateway = stubMediaStoreGateway, + backgroundWorkObserver = stubBackgroundWorkObserver, base64ToBitmapConverter = stubBase64ToBitmapConverter, localDataSource = stubLocalDataSource, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, remoteDataSource = stubRemoteDataSource, preferenceManager = stubPreferenceManager, textToImageDemo = stubTextToImageDemo, imageToImageDemo = stubImageToImageDemo, - backgroundWorkObserver = stubBackgroundWorkObserver, ) @Before diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionHyperNetworksRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionHyperNetworksRepositoryImplTest.kt similarity index 96% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionHyperNetworksRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionHyperNetworksRepositoryImplTest.kt index 7764e2c53..02bed4c07 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionHyperNetworksRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionHyperNetworksRepositoryImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionHyperNetworks -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionHyperNetworksDataSource +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionHyperNetworks +import dev.minios.pdaiv1.domain.datasource.StableDiffusionHyperNetworksDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionModelsRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionModelsRepositoryImplTest.kt similarity index 95% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionModelsRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionModelsRepositoryImplTest.kt index 6ab990f65..c9ea4a756 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionModelsRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionModelsRepositoryImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository import com.nhaarman.mockitokotlin2.mock -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionModels -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionSamplers -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionModelsDataSource +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionModels +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionSamplers +import dev.minios.pdaiv1.domain.datasource.StableDiffusionModelsDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionSamplersRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionSamplersRepositoryImplTest.kt similarity index 94% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionSamplersRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionSamplersRepositoryImplTest.kt index d703e3b13..084c331ec 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/StableDiffusionSamplersRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/StableDiffusionSamplersRepositoryImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockStableDiffusionSamplers -import com.shifthackz.aisdv1.domain.datasource.StableDiffusionSamplersDataSource +import dev.minios.pdaiv1.data.mocks.mockStableDiffusionSamplers +import dev.minios.pdaiv1.domain.datasource.StableDiffusionSamplersDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/SupportersRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/SupportersRepositoryImplTest.kt similarity index 96% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/SupportersRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/SupportersRepositoryImplTest.kt index c1eaa7799..968401f8b 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/SupportersRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/SupportersRepositoryImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockSupporters -import com.shifthackz.aisdv1.domain.datasource.SupportersDataSource +import dev.minios.pdaiv1.data.mocks.mockSupporters +import dev.minios.pdaiv1.domain.datasource.SupportersDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/SwarmUiGenerationRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/SwarmUiGenerationRepositoryImplTest.kt similarity index 85% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/SwarmUiGenerationRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/SwarmUiGenerationRepositoryImplTest.kt index 3d0f8e771..0206fc7dd 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/SwarmUiGenerationRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/SwarmUiGenerationRepositoryImplTest.kt @@ -1,15 +1,17 @@ -package com.shifthackz.aisdv1.data.repository - -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.data.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.data.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.datasource.GenerationResultDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiGenerationDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.gateway.MediaStoreGateway -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +package dev.minios.pdaiv1.data.repository + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashEncoder +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.data.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.datasource.GenerationResultDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiGenerationDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single @@ -26,15 +28,19 @@ class SwarmUiGenerationRepositoryImplTest { private val stubSession = mockk() private val stubPreferenceManager = mockk() private val stubBackgroundWorkObserver = mockk() - + private val stubMediaFileManager = mockk() + private val stubBlurHashEncoder = mockk() + private val repository = SwarmUiGenerationRepositoryImpl( mediaStoreGateway = stubMediaStoreGateway, base64ToBitmapConverter = stubBase64ToBitmapConverter, localDataSource = stubLocalDataSource, - remoteDataSource = stubRemoteDataSource, - session = stubSession, - preferenceManager = stubPreferenceManager, backgroundWorkObserver = stubBackgroundWorkObserver, + mediaFileManager = stubMediaFileManager, + blurHashEncoder = stubBlurHashEncoder, + preferenceManager = stubPreferenceManager, + session = stubSession, + remoteDataSource = stubRemoteDataSource, ) @Before diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/SwarmUiModelsRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/SwarmUiModelsRepositoryImplTest.kt similarity index 95% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/SwarmUiModelsRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/SwarmUiModelsRepositoryImplTest.kt index ca6cb6cf8..182cc692c 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/SwarmUiModelsRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/SwarmUiModelsRepositoryImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockSwarmUiModels -import com.shifthackz.aisdv1.domain.datasource.SwarmUiModelsDataSource -import com.shifthackz.aisdv1.domain.datasource.SwarmUiSessionDataSource +import dev.minios.pdaiv1.data.mocks.mockSwarmUiModels +import dev.minios.pdaiv1.domain.datasource.SwarmUiModelsDataSource +import dev.minios.pdaiv1.domain.datasource.SwarmUiSessionDataSource import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/TemporaryGenerationResultRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/TemporaryGenerationResultRepositoryImplTest.kt similarity index 89% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/TemporaryGenerationResultRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/TemporaryGenerationResultRepositoryImplTest.kt index 989f80600..f0a67692e 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/TemporaryGenerationResultRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/TemporaryGenerationResultRepositoryImplTest.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository -import com.shifthackz.aisdv1.data.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.data.mocks.mockAiGenerationResult import org.junit.Test class TemporaryGenerationResultRepositoryImplTest { diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/WakeLockRepositoryImplTest.kt b/data/src/test/java/dev/minios/pdaiv1/data/repository/WakeLockRepositoryImplTest.kt similarity index 96% rename from data/src/test/java/com/shifthackz/aisdv1/data/repository/WakeLockRepositoryImplTest.kt rename to data/src/test/java/dev/minios/pdaiv1/data/repository/WakeLockRepositoryImplTest.kt index 6e7964828..47bab5c7c 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/WakeLockRepositoryImplTest.kt +++ b/data/src/test/java/dev/minios/pdaiv1/data/repository/WakeLockRepositoryImplTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.data.repository +package dev.minios.pdaiv1.data.repository import android.os.PowerManager import io.mockk.every diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 5f4b0b294..13890a847 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.demo" + namespace = "dev.minios.pdaiv1.demo" } dependencies { diff --git a/demo/src/main/java/com/shifthackz/aisdv1/demo/TextToImageDemoImpl.kt b/demo/src/main/java/com/shifthackz/aisdv1/demo/TextToImageDemoImpl.kt deleted file mode 100644 index 68a8f3092..000000000 --- a/demo/src/main/java/com/shifthackz/aisdv1/demo/TextToImageDemoImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.shifthackz.aisdv1.demo - -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.demo.serialize.DemoDataSerializer -import com.shifthackz.aisdv1.domain.demo.TextToImageDemo -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload - -internal class TextToImageDemoImpl( - override val demoDataSerializer: DemoDataSerializer, - private val timeProvider: TimeProvider, -) : TextToImageDemo, DemoFeature { - - override fun mapper(input: TextToImagePayload, base64: String) = AiGenerationResult( - id = 0L, - image = base64, - inputImage = "", - createdAt = timeProvider.currentDate(), - type = AiGenerationResult.Type.TEXT_TO_IMAGE, - prompt = input.prompt, - negativePrompt = input.negativePrompt, - width = input.width, - height = input.height, - samplingSteps = input.samplingSteps, - cfgScale = input.cfgScale, - restoreFaces = input.restoreFaces, - sampler = input.sampler, - seed = timeProvider.currentTimeMillis().toString(), - subSeed = timeProvider.currentTimeMillis().toString(), - subSeedStrength = 0f, - denoisingStrength = 0f, - hidden = false, - ) - - override fun getDemoBase64(payload: TextToImagePayload) = execute(payload) -} diff --git a/demo/src/main/java/com/shifthackz/aisdv1/demo/di/DemoModule.kt b/demo/src/main/java/com/shifthackz/aisdv1/demo/di/DemoModule.kt deleted file mode 100644 index b1f57c648..000000000 --- a/demo/src/main/java/com/shifthackz/aisdv1/demo/di/DemoModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.demo.di - -import com.shifthackz.aisdv1.demo.ImageToImageDemoImpl -import com.shifthackz.aisdv1.demo.TextToImageDemoImpl -import com.shifthackz.aisdv1.demo.serialize.DemoDataSerializer -import com.shifthackz.aisdv1.domain.demo.ImageToImageDemo -import com.shifthackz.aisdv1.domain.demo.TextToImageDemo -import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.module - -val demoModule = module { - singleOf(::DemoDataSerializer) - factoryOf(::TextToImageDemoImpl) bind TextToImageDemo::class - factoryOf(::ImageToImageDemoImpl) bind ImageToImageDemo::class -} diff --git a/demo/src/main/java/com/shifthackz/aisdv1/demo/DemoFeature.kt b/demo/src/main/java/dev/minios/pdaiv1/demo/DemoFeature.kt similarity index 82% rename from demo/src/main/java/com/shifthackz/aisdv1/demo/DemoFeature.kt rename to demo/src/main/java/dev/minios/pdaiv1/demo/DemoFeature.kt index 44e623c35..9c5bb4a32 100644 --- a/demo/src/main/java/com/shifthackz/aisdv1/demo/DemoFeature.kt +++ b/demo/src/main/java/dev/minios/pdaiv1/demo/DemoFeature.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.demo +package dev.minios.pdaiv1.demo -import com.shifthackz.aisdv1.demo.serialize.DemoDataSerializer -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.demo.serialize.DemoDataSerializer +import dev.minios.pdaiv1.domain.entity.AiGenerationResult import io.reactivex.rxjava3.core.Single import java.util.concurrent.TimeUnit import kotlin.random.Random diff --git a/demo/src/main/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImpl.kt b/demo/src/main/java/dev/minios/pdaiv1/demo/ImageToImageDemoImpl.kt similarity index 75% rename from demo/src/main/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImpl.kt rename to demo/src/main/java/dev/minios/pdaiv1/demo/ImageToImageDemoImpl.kt index e46a05ed3..bb63cee7b 100644 --- a/demo/src/main/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImpl.kt +++ b/demo/src/main/java/dev/minios/pdaiv1/demo/ImageToImageDemoImpl.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.demo +package dev.minios.pdaiv1.demo -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.demo.serialize.DemoDataSerializer -import com.shifthackz.aisdv1.domain.demo.ImageToImageDemo -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.demo.serialize.DemoDataSerializer +import dev.minios.pdaiv1.domain.demo.ImageToImageDemo +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload internal class ImageToImageDemoImpl( override val demoDataSerializer: DemoDataSerializer, @@ -30,6 +30,7 @@ internal class ImageToImageDemoImpl( subSeedStrength = 0f, denoisingStrength = 0f, hidden = false, + modelName = input.modelName, ) override fun getDemoBase64(payload: ImageToImagePayload) = execute(payload) diff --git a/demo/src/main/java/dev/minios/pdaiv1/demo/TextToImageDemoImpl.kt b/demo/src/main/java/dev/minios/pdaiv1/demo/TextToImageDemoImpl.kt new file mode 100644 index 000000000..9f219c8f9 --- /dev/null +++ b/demo/src/main/java/dev/minios/pdaiv1/demo/TextToImageDemoImpl.kt @@ -0,0 +1,37 @@ +package dev.minios.pdaiv1.demo + +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.demo.serialize.DemoDataSerializer +import dev.minios.pdaiv1.domain.demo.TextToImageDemo +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.TextToImagePayload + +internal class TextToImageDemoImpl( + override val demoDataSerializer: DemoDataSerializer, + private val timeProvider: TimeProvider, +) : TextToImageDemo, DemoFeature { + + override fun mapper(input: TextToImagePayload, base64: String) = AiGenerationResult( + id = 0L, + image = base64, + inputImage = "", + createdAt = timeProvider.currentDate(), + type = AiGenerationResult.Type.TEXT_TO_IMAGE, + prompt = input.prompt, + negativePrompt = input.negativePrompt, + width = input.width, + height = input.height, + samplingSteps = input.samplingSteps, + cfgScale = input.cfgScale, + restoreFaces = input.restoreFaces, + sampler = input.sampler, + seed = timeProvider.currentTimeMillis().toString(), + subSeed = timeProvider.currentTimeMillis().toString(), + subSeedStrength = 0f, + denoisingStrength = 0f, + hidden = false, + modelName = input.modelName, + ) + + override fun getDemoBase64(payload: TextToImagePayload) = execute(payload) +} diff --git a/demo/src/main/java/dev/minios/pdaiv1/demo/di/DemoModule.kt b/demo/src/main/java/dev/minios/pdaiv1/demo/di/DemoModule.kt new file mode 100644 index 000000000..3a98d2bc7 --- /dev/null +++ b/demo/src/main/java/dev/minios/pdaiv1/demo/di/DemoModule.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.demo.di + +import dev.minios.pdaiv1.demo.ImageToImageDemoImpl +import dev.minios.pdaiv1.demo.TextToImageDemoImpl +import dev.minios.pdaiv1.demo.serialize.DemoDataSerializer +import dev.minios.pdaiv1.domain.demo.ImageToImageDemo +import dev.minios.pdaiv1.domain.demo.TextToImageDemo +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val demoModule = module { + singleOf(::DemoDataSerializer) + factoryOf(::TextToImageDemoImpl) bind TextToImageDemo::class + factoryOf(::ImageToImageDemoImpl) bind ImageToImageDemo::class +} diff --git a/demo/src/main/java/com/shifthackz/aisdv1/demo/serialize/DemoDataSerializer.kt b/demo/src/main/java/dev/minios/pdaiv1/demo/serialize/DemoDataSerializer.kt similarity index 99% rename from demo/src/main/java/com/shifthackz/aisdv1/demo/serialize/DemoDataSerializer.kt rename to demo/src/main/java/dev/minios/pdaiv1/demo/serialize/DemoDataSerializer.kt index 7d2d25412..998dc37b5 100644 --- a/demo/src/main/java/com/shifthackz/aisdv1/demo/serialize/DemoDataSerializer.kt +++ b/demo/src/main/java/dev/minios/pdaiv1/demo/serialize/DemoDataSerializer.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.demo.serialize +package dev.minios.pdaiv1.demo.serialize internal class DemoDataSerializer { diff --git a/demo/src/test/java/com/shifthackz/aisdv1/demo/mocks/ImageToImagePayloadMocks.kt b/demo/src/test/java/com/shifthackz/aisdv1/demo/mocks/ImageToImagePayloadMocks.kt deleted file mode 100644 index 1da3962ed..000000000 --- a/demo/src/test/java/com/shifthackz/aisdv1/demo/mocks/ImageToImagePayloadMocks.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.shifthackz.aisdv1.demo.mocks - -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload - -val mockImageToImagePayload = ImageToImagePayload( - base64Image = "", - base64MaskImage = "", - denoisingStrength = 7f, - prompt = "prompt", - negativePrompt = "negative", - samplingSteps = 12, - cfgScale = 0.7f, - width = 512, - height = 512, - restoreFaces = true, - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - sampler = "sampler", - nsfw = true, - batchCount = 1, - inPaintingMaskInvert = 0, - inPaintFullResPadding = 0, - inPaintingFill = 0, - inPaintFullRes = false, - maskBlur = 0, - stabilityAiClipGuidance = null, - stabilityAiStylePreset = null, -) diff --git a/demo/src/test/java/com/shifthackz/aisdv1/demo/mocks/TextToImagePayloadMocks.kt b/demo/src/test/java/com/shifthackz/aisdv1/demo/mocks/TextToImagePayloadMocks.kt deleted file mode 100644 index 819dc51c2..000000000 --- a/demo/src/test/java/com/shifthackz/aisdv1/demo/mocks/TextToImagePayloadMocks.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.demo.mocks - -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload - -val mockTextToImagePayload = TextToImagePayload( - prompt = "prompt", - negativePrompt = "negative", - samplingSteps = 12, - cfgScale = 0.7f, - width = 512, - height = 512, - restoreFaces = true, - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - sampler = "sampler", - nsfw = true, - batchCount = 1, - quality = null, - style = null, - openAiModel = null, - stabilityAiClipGuidance = null, - stabilityAiStylePreset = null, -) diff --git a/demo/src/test/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImplTest.kt b/demo/src/test/java/dev/minios/pdaiv1/demo/ImageToImageDemoImplTest.kt similarity index 82% rename from demo/src/test/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImplTest.kt rename to demo/src/test/java/dev/minios/pdaiv1/demo/ImageToImageDemoImplTest.kt index 77b5c39c3..b0454f7ed 100644 --- a/demo/src/test/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImplTest.kt +++ b/demo/src/test/java/dev/minios/pdaiv1/demo/ImageToImageDemoImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.demo +package dev.minios.pdaiv1.demo -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.demo.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.demo.serialize.DemoDataSerializer +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.demo.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.demo.serialize.DemoDataSerializer import io.mockk.every import io.mockk.mockk import org.junit.Before diff --git a/demo/src/test/java/com/shifthackz/aisdv1/demo/TextToImageDemoImplTest.kt b/demo/src/test/java/dev/minios/pdaiv1/demo/TextToImageDemoImplTest.kt similarity index 82% rename from demo/src/test/java/com/shifthackz/aisdv1/demo/TextToImageDemoImplTest.kt rename to demo/src/test/java/dev/minios/pdaiv1/demo/TextToImageDemoImplTest.kt index a96626e4b..be4b4ec56 100644 --- a/demo/src/test/java/com/shifthackz/aisdv1/demo/TextToImageDemoImplTest.kt +++ b/demo/src/test/java/dev/minios/pdaiv1/demo/TextToImageDemoImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.demo +package dev.minios.pdaiv1.demo -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.demo.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.demo.serialize.DemoDataSerializer +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.demo.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.demo.serialize.DemoDataSerializer import io.mockk.every import io.mockk.mockk import org.junit.Before diff --git a/demo/src/test/java/dev/minios/pdaiv1/demo/mocks/ImageToImagePayloadMocks.kt b/demo/src/test/java/dev/minios/pdaiv1/demo/mocks/ImageToImagePayloadMocks.kt new file mode 100644 index 000000000..61c099175 --- /dev/null +++ b/demo/src/test/java/dev/minios/pdaiv1/demo/mocks/ImageToImagePayloadMocks.kt @@ -0,0 +1,29 @@ +package dev.minios.pdaiv1.demo.mocks + +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload + +val mockImageToImagePayload = ImageToImagePayload( + base64Image = "", + base64MaskImage = "", + denoisingStrength = 7f, + prompt = "prompt", + negativePrompt = "negative", + samplingSteps = 12, + cfgScale = 0.7f, + width = 512, + height = 512, + restoreFaces = true, + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + sampler = "sampler", + nsfw = true, + batchCount = 1, + inPaintingMaskInvert = 0, + inPaintFullResPadding = 0, + inPaintingFill = 0, + inPaintFullRes = false, + maskBlur = 0, + stabilityAiClipGuidance = null, + stabilityAiStylePreset = null, +) diff --git a/demo/src/test/java/dev/minios/pdaiv1/demo/mocks/TextToImagePayloadMocks.kt b/demo/src/test/java/dev/minios/pdaiv1/demo/mocks/TextToImagePayloadMocks.kt new file mode 100644 index 000000000..2523a26e3 --- /dev/null +++ b/demo/src/test/java/dev/minios/pdaiv1/demo/mocks/TextToImagePayloadMocks.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.demo.mocks + +import dev.minios.pdaiv1.domain.entity.TextToImagePayload + +val mockTextToImagePayload = TextToImagePayload( + prompt = "prompt", + negativePrompt = "negative", + samplingSteps = 12, + cfgScale = 0.7f, + width = 512, + height = 512, + restoreFaces = true, + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + sampler = "sampler", + nsfw = true, + batchCount = 1, + quality = null, + style = null, + openAiModel = null, + stabilityAiClipGuidance = null, + stabilityAiStylePreset = null, +) diff --git a/docs/CNAME b/docs/CNAME index 0f808f567..dd069354d 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -sdai.moroz.cc \ No newline at end of file +pdai.minios.dev \ No newline at end of file diff --git a/docs/app-ads.txt b/docs/app-ads.txt deleted file mode 100644 index d0292b1c4..000000000 --- a/docs/app-ads.txt +++ /dev/null @@ -1,9 +0,0 @@ -applovin.com, b31d4c3b02df6a3d84596566b17b0318, DIRECT -loopme.com, 11303, RESELLER, 6c8d5f95897a5a3b -rubiconproject.com, 16356, RESELLER, 0bfd66d529a55807 -rubiconproject.com, 20744, RESELLER, 0bfd66d529a55807 -pubmatic.com, 158154, RESELLER, 5d62403b186f2ace -pubmatic.com, 158862, RESELLER, 5d62403b186f2ace -pubmatic.com, 159509, RESELLER, 5d62403b186f2ace -xandr.com, 13799, RESELLER -media.net, 8CUD06Z68, RESELLER diff --git a/docs/assets/4pda.png b/docs/assets/4pda.png deleted file mode 100644 index c6618de24..000000000 Binary files a/docs/assets/4pda.png and /dev/null differ diff --git a/docs/assets/bwpdai.svg b/docs/assets/bwpdai.svg new file mode 100644 index 000000000..19a3777ba --- /dev/null +++ b/docs/assets/bwpdai.svg @@ -0,0 +1,46 @@ + + + + diff --git a/docs/assets/fdroid.png b/docs/assets/fdroid.png deleted file mode 100644 index 6c6deaf33..000000000 Binary files a/docs/assets/fdroid.png and /dev/null differ diff --git a/docs/assets/github-header-image.png b/docs/assets/github-header-image.png deleted file mode 100644 index e4842a01c..000000000 Binary files a/docs/assets/github-header-image.png and /dev/null differ diff --git a/docs/assets/google_play.png b/docs/assets/google_play.png deleted file mode 100644 index 3d0962f1b..000000000 Binary files a/docs/assets/google_play.png and /dev/null differ diff --git a/docs/assets/pdai.png b/docs/assets/pdai.png new file mode 100644 index 000000000..a12218b9f Binary files /dev/null and b/docs/assets/pdai.png differ diff --git a/docs/assets/pdai.svg b/docs/assets/pdai.svg new file mode 100644 index 000000000..f0a671fd4 --- /dev/null +++ b/docs/assets/pdai.svg @@ -0,0 +1,549 @@ + + + + diff --git a/docs/assets/sdai.png b/docs/assets/sdai.png deleted file mode 100644 index 810dd0920..000000000 Binary files a/docs/assets/sdai.png and /dev/null differ diff --git a/docs/assets/tlogo_1024.png b/docs/assets/tlogo_1024.png new file mode 100644 index 000000000..ab7f9073f Binary files /dev/null and b/docs/assets/tlogo_1024.png differ diff --git a/docs/assets/tlogo_256.png b/docs/assets/tlogo_256.png new file mode 100644 index 000000000..b4ee9cbfe Binary files /dev/null and b/docs/assets/tlogo_256.png differ diff --git a/docs/assets/wlogo_1024.png b/docs/assets/wlogo_1024.png new file mode 100644 index 000000000..40e6b5c0e Binary files /dev/null and b/docs/assets/wlogo_1024.png differ diff --git a/docs/css/style.css b/docs/css/style.css new file mode 100644 index 000000000..4a3560dec --- /dev/null +++ b/docs/css/style.css @@ -0,0 +1,315 @@ +:root { + --primary-color: #667eea; + --secondary-color: #764ba2; + --accent-color: #ffd700; + --bg-color: #0f172a; + --text-color: #e2e8f0; + --card-bg: rgba(30, 41, 59, 0.7); + --card-border: rgba(148, 163, 184, 0.1); + --font-main: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: var(--font-main); + background-color: var(--bg-color); + color: var(--text-color); + line-height: 1.6; + overflow-x: hidden; +} + +/* Background Gradient Animation */ +body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(circle at 50% 0%, #2d1b4e 0%, var(--bg-color) 50%); + z-index: -1; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Header */ +header { + padding: 20px 0; + position: fixed; + width: 100%; + top: 0; + z-index: 100; + backdrop-filter: blur(10px); + background: rgba(15, 23, 42, 0.8); + border-bottom: 1px solid var(--card-border); +} + +nav { + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + font-size: 1.5rem; + font-weight: 700; + color: white; + text-decoration: none; + display: flex; + align-items: center; + gap: 10px; +} + +.logo img { + height: 32px; + width: auto; +} + +.nav-links a { + color: var(--text-color); + text-decoration: none; + margin-left: 20px; + font-weight: 500; + transition: color 0.3s; +} + +.nav-links a:hover { + color: var(--primary-color); +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + padding: 10px 24px; + border-radius: 50px; + text-decoration: none; + font-weight: 600; + transition: transform 0.2s, box-shadow 0.2s; + display: inline-block; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(118, 75, 162, 0.4); +} + +.btn-telegram { + background: #229ED9; + color: white !important; + padding: 8px 20px; + border-radius: 50px; + text-decoration: none; + font-weight: 600; + transition: transform 0.2s, box-shadow 0.2s; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.btn-telegram:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(34, 158, 217, 0.4); + background: #1b7db3; +} + +.qr-code-container { + margin-top: 30px; + margin-bottom: 10px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + animation: fadeIn 1s ease-out; +} + +.qr-code-container img { + border-radius: 15px; + border: 4px solid white; + box-shadow: 0 10px 30px rgba(0,0,0,0.3); + transition: transform 0.3s ease; +} + +.qr-code-container img:hover { + transform: scale(1.05); +} + +.qr-code-container p { + font-size: 0.9rem; + color: var(--text-color); + opacity: 0.8; + margin-bottom: 0 !important; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Hero Section */ +.hero { + padding: 160px 0 80px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.hero-icon { + width: 120px; + height: 120px; + border-radius: 24px; + margin-bottom: 30px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + animation: float 6s ease-in-out infinite; +} + +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-10px); } + 100% { transform: translateY(0px); } +} + +.hero h1 { + font-size: 3.5rem; + font-weight: 800; + margin-bottom: 20px; + background: linear-gradient(to right, #fff, #a5b4fc); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + line-height: 1.2; +} + +.hero p { + font-size: 1.25rem; + color: #94a3b8; + max-width: 600px; + margin: 0 auto 40px; +} + +.hero-buttons { + display: flex; + gap: 20px; + justify-content: center; + margin-bottom: 60px; +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.1); + color: white; + padding: 10px 24px; + border-radius: 50px; + text-decoration: none; + font-weight: 600; + transition: background 0.3s; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.2); +} + +.hero-image { + max-width: 100%; + border-radius: 20px; + box-shadow: 0 20px 80px rgba(0, 0, 0, 0.5); + border: 1px solid var(--card-border); + margin-top: 40px; +} + +/* Features Grid */ +.features { + padding: 80px 0; +} + +.section-title { + text-align: center; + font-size: 2.5rem; + margin-bottom: 60px; + color: white; +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; +} + +.feature-card { + background: var(--card-bg); + border: 1px solid var(--card-border); + padding: 30px; + border-radius: 20px; + transition: transform 0.3s; +} + +.feature-card:hover { + transform: translateY(-5px); + border-color: var(--primary-color); +} + +.feature-icon { + font-size: 2rem; + margin-bottom: 20px; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.feature-card h3 { + font-size: 1.5rem; + margin-bottom: 15px; + color: white; +} + +.feature-card p { + color: #94a3b8; +} + +/* Footer */ +footer { + padding: 60px 0 30px; + border-top: 1px solid var(--card-border); + margin-top: 80px; + text-align: center; + color: #64748b; +} + +.footer-links { + margin-bottom: 30px; +} + +.footer-links a { + color: #94a3b8; + text-decoration: none; + margin: 0 15px; + transition: color 0.3s; +} + +.footer-links a:hover { + color: var(--primary-color); +} + +/* Responsive */ +@media (max-width: 768px) { + .hero h1 { + font-size: 2.5rem; + } + + .hero-buttons { + flex-direction: column; + } + + .nav-links { + display: none; /* Simple mobile menu hide for now */ + } +} diff --git a/docs/index.html b/docs/index.html index 1a2d6f8e1..30b365276 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,203 +1,122 @@ - - - - - SDAI Android App - - - - - - - - - - - - - - - - - - - + + - - - - -
-
-
-
- - assets/sdai.png - - -
-
- -

- Stable Diffusion AI for your Android smartphone.

- - -

Generate Stable Diffusion AI - assets on your own Automatic1111 instance.

- -

- Stable Diffusion AI is highly customizable and adaptable, allowing you to - connect to any user-defined automatic WebUI server and adjust the settings to - meet your specific needs. With its fast and reliable image generation - capabilities, Stable Diffusion AI is the perfect tool for developers, designers, - and anyone who needs to quickly generate images on the go. -

- Try Stable Diffusion AI today and experience the power and convenience of mobile - image generation at your fingertips! -

- - -
-
- - -
-
-
-
+ + +
+
+
+ Pocket Diffusion Icon +

AI Image Generation
in your Pocket

+

Generate stunning AI art directly on your Android device.
Connect to powerful backends or run locally with privacy in mind.

+ + -

SDAI Android App © Dmitriy Moroz

+
+ QR Code to Download +

Scan to Download

+
+ App Screenshot
+
+ +
+
+

Why Pocket Diffusion?

+
+
+ +

On-Device Generation

+

Run Stable Diffusion models locally on your device using MediaPipe. No internet connection required for generation.

+
+
+ +

Remote Backends

+

Connect to your own Automatic1111 or ComfyUI server for unlimited power and model flexibility.

+
+
+ +

Privacy First

+

Your prompts and images stay yours. No data collection, no tracking, completely open source.

+
+
+ +

User Friendly

+

Clean, modern Material You interface designed for mobile. Easy to use for beginners and pros alike.

+
+
+ +

Open Source

+

Fully open source code. Audit, contribute, or build your own version. Community driven development.

+
+
+ +

Fast & Efficient

+

Optimized for Android devices. Uses hardware acceleration where available for best performance.

+
+
+
+
+
+ +
-
- - + diff --git a/docs/mediapipe.json b/docs/mediapipe.json index 501b3424e..90fa8f969 100644 --- a/docs/mediapipe.json +++ b/docs/mediapipe.json @@ -5,8 +5,7 @@ "size": "1.9 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-26082024/stable-diffusion-v1-5.zip", - "https://share.moroz.cc/SDAI/MediaPipe/stable-diffusion-v1-5.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-26082024/stable-diffusion-v1-5.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-26082024/stable-diffusion-v1-5.zip" ], "metadata": { "src": "https://huggingface.co/runwayml/stable-diffusion-v1-5/blob/main/v1-5-pruned-emaonly.ckpt" @@ -18,8 +17,7 @@ "size": "1.82 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-28082024/minisd.zip", - "https://share.moroz.cc/SDAI/MediaPipe/minisd.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-28082024/minisd.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-28082024/minisd.zip" ], "metadata": { "src": "https://huggingface.co/justinpinkney/miniSD/blob/main/miniSD.ckpt" @@ -31,8 +29,7 @@ "size": "1.82 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-28082024/waifu-diffusion-v1-4.zip", - "https://share.moroz.cc/SDAI/MediaPipe/waifu-diffusion-v1-4.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-28082024/waifu-diffusion-v1-4.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-28082024/waifu-diffusion-v1-4.zip" ], "metadata": { "src": "https://huggingface.co/hakurei/waifu-diffusion-v1-4/blob/main/models/wd-1-3-penultimate-ucg-cont.ckpt" @@ -44,8 +41,7 @@ "size": "1.79 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-19092024/aniverse_v50.zip", - "https://share.moroz.cc/SDAI/MediaPipe/aniverse_v50.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-19092024/aniverse_v50.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-MediaPipe/releases/download/patch-19092024/aniverse_v50.zip" ], "metadata": { "src": "https://civitai.com/models/107842/aniverse" diff --git a/docs/mnn.json b/docs/mnn.json new file mode 100644 index 000000000..e51f47d12 --- /dev/null +++ b/docs/mnn.json @@ -0,0 +1,62 @@ +[ + { + "id": "mnn-anything-v5", + "name": "Anything V5 (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/AnythingV5.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "mnn" + } + }, + { + "id": "mnn-qteamix", + "name": "QteaMix (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/QteaMix.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "mnn" + } + }, + { + "id": "mnn-cuteyukimix", + "name": "CuteYukiMix (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/CuteYukiMix.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "mnn" + } + }, + { + "id": "mnn-absolutereality", + "name": "Absolute Reality (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/AbsoluteReality.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "mnn" + } + }, + { + "id": "mnn-chilloutmix", + "name": "ChilloutMix (CPU/MNN)", + "size": "1.2 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/ChilloutMix.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "mnn" + } + } +] diff --git a/docs/models.json b/docs/models.json index 1755e9594..96288645f 100644 --- a/docs/models.json +++ b/docs/models.json @@ -5,8 +5,7 @@ "size": "1.2 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/chilloutmix.zip", - "https://share.moroz.cc/SDAI/ONNX/chilloutmix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/chilloutmix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/chilloutmix.zip" ] }, { @@ -15,8 +14,7 @@ "size": "1.01 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/majicmix.zip", - "https://share.moroz.cc/SDAI/ONNX/majicmix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/majicmix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/majicmix.zip" ] }, { @@ -25,8 +23,7 @@ "size": "1.8 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/realvision.zip", - "https://share.moroz.cc/SDAI/ONNX/realvision.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/realvision.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-14022024/realvision.zip" ] }, { @@ -35,8 +32,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/analogMadness.zip", - "https://share.moroz.cc/SDAI/ONNX/analogMadness.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/analogMadness.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/analogMadness.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/miena%2Clyriel%2Ccetus%2Canalogmadness/analogMadness*" @@ -48,8 +44,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/aniflatmix.zip", - "https://share.moroz.cc/SDAI/ONNX/aniflatmix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/aniflatmix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/aniflatmix.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/landscape_modelsfp32noslicing/aniflatmix*" @@ -61,8 +56,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/beautifulRealistic_v60.zip", - "https://share.moroz.cc/SDAI/ONNX/beautifulRealistic_v60.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/beautifulRealistic_v60.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/beautifulRealistic_v60.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/beautifulrealv6%2Cmajicmix%2Ccyberreal%2Cepicrealism%2C/beautifulRealistic_v60" @@ -74,8 +68,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/cetusMix.zip", - "https://share.moroz.cc/SDAI/ONNX/cetusMix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/cetusMix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/cetusMix.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/miena%2Clyriel%2Ccetus%2Canalogmadness/cetusMix*" @@ -87,8 +80,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0.zip", - "https://share.moroz.cc/SDAI/ONNX/counterfit-v3.0.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Counterfeit-V3.0%2CA-ZovyaRPGArtistTools%2CBabes%2CDreamlikeDiffusion/counterfeit*" @@ -100,8 +92,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0-babes.zip", - "https://share.moroz.cc/SDAI/ONNX/counterfit-v3.0-babes.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0-babes.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0-babes.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Counterfeit-V3.0%2CA-ZovyaRPGArtistTools%2CBabes%2CDreamlikeDiffusion/babes*" @@ -113,8 +104,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0-zovyarpg.zip", - "https://share.moroz.cc/SDAI/ONNX/counterfit-v3.0-zovyarpg.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0-zovyarpg.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/counterfit-v3.0-zovyarpg.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Counterfeit-V3.0%2CA-ZovyaRPGArtistTools%2CBabes%2CDreamlikeDiffusion/zovyarpg*" @@ -126,8 +116,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/cyberrealistic_v32.zip", - "https://share.moroz.cc/SDAI/ONNX/cyberrealistic_v32.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/cyberrealistic_v32.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/cyberrealistic_v32.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/beautifulrealv6%2Cmajicmix%2Ccyberreal%2Cepicrealism%2C/cyberrealistic_v32" @@ -139,8 +128,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/darkjunglemix.zip", - "https://share.moroz.cc/SDAI/ONNX/darkjunglemix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/darkjunglemix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/darkjunglemix.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Realisticfantasy%2CdarkjungleMix%2CRestlessExistence%2CJucy666/darkjunglemix*" @@ -152,8 +140,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/deliberate.zip", - "https://share.moroz.cc/SDAI/ONNX/deliberate.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/deliberate.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/deliberate.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/deliberate%2Cdreamshaper/deliberate*" @@ -165,8 +152,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/dreamshaper.zip", - "https://share.moroz.cc/SDAI/ONNX/dreamshaper.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/dreamshaper.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/dreamshaper.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/deliberate%2Cdreamshaper/dreamshaper*" @@ -178,8 +164,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/dreamlikep_r_2.zip", - "https://share.moroz.cc/SDAI/ONNX/dreamlikep_r_2.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/dreamlikep_r_2.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/dreamlikep_r_2.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/dreamlikep_r_2" @@ -191,8 +176,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/epicrealism_pureEvolutionV4.zip", - "https://share.moroz.cc/SDAI/ONNX/epicrealism_pureEvolutionV4.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/epicrealism_pureEvolutionV4.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/epicrealism_pureEvolutionV4.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/beautifulrealv6%2Cmajicmix%2Ccyberreal%2Cepicrealism%2C/epicrealism_pureEvolutionV4" @@ -204,8 +188,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/fantasticmix.zip", - "https://share.moroz.cc/SDAI/ONNX/fantasticmix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/fantasticmix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/fantasticmix.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/othermodels1/fantasticmix*" @@ -217,8 +200,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/ICBINP.zip", - "https://share.moroz.cc/SDAI/ONNX/ICBINP.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/ICBINP.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/ICBINP.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/ICBINP" @@ -230,8 +212,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/Jucy666.zip", - "https://share.moroz.cc/SDAI/ONNX/Jucy666.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/Jucy666.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/Jucy666.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Realisticfantasy%2CdarkjungleMix%2CRestlessExistence%2CJucy666/Jucy666*" @@ -243,8 +224,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/landscape.zip", - "https://share.moroz.cc/SDAI/ONNX/landscape.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/landscape.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/landscape.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/landscape_modelsfp32noslicing/landscape*" @@ -256,8 +236,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/lyriel.zip", - "https://share.moroz.cc/SDAI/ONNX/lyriel.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/lyriel.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/lyriel.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/miena%2Clyriel%2Ccetus%2Canalogmadness/lyriel*" @@ -269,8 +248,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/majicmixRealistic_betterV2V25.zip", - "https://share.moroz.cc/SDAI/ONNX/majicmixRealistic_betterV2V25.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/majicmixRealistic_betterV2V25.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/majicmixRealistic_betterV2V25.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/beautifulrealv6%2Cmajicmix%2Ccyberreal%2Cepicrealism%2C/majicmixRealistic_betterV2V25" @@ -282,8 +260,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/meinamix.zip", - "https://share.moroz.cc/SDAI/ONNX/meinamix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/meinamix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/meinamix.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/miena%2Clyriel%2Ccetus%2Canalogmadness/meinamix*" @@ -295,8 +272,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/meinaunreal.zip", - "https://share.moroz.cc/SDAI/ONNX/meinaunreal.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/meinaunreal.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/meinaunreal.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/othermodels1/meinaunreal*" @@ -308,8 +284,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/mixreal.zip", - "https://share.moroz.cc/SDAI/ONNX/mixreal.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/mixreal.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/mixreal.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/landscape_modelsfp32noslicing/mixreal*" @@ -321,8 +296,7 @@ "size": "1.1 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/NED.zip", - "https://share.moroz.cc/SDAI/ONNX/NED.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/NED.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/NED.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Never-ending-dream(NED(vae))" @@ -334,8 +308,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/realexistence.zip", - "https://share.moroz.cc/SDAI/ONNX/realexistence.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/realexistence.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/realexistence.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Realisticfantasy%2CdarkjungleMix%2CRestlessExistence%2CJucy666/realexistence*" @@ -347,8 +320,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/realisticfantasy.zip", - "https://share.moroz.cc/SDAI/ONNX/realisticfantasy.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/realisticfantasy.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/realisticfantasy.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/Realisticfantasy%2CdarkjungleMix%2CRestlessExistence%2CJucy666/realisticfantasy*" @@ -360,8 +332,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/sunshinemix.zip", - "https://share.moroz.cc/SDAI/ONNX/sunshinemix.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/sunshinemix.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/sunshinemix.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/othermodels1/sunshinemix*" @@ -373,8 +344,7 @@ "size": "1.0 Gb", "sources": [ "https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/universestable.zip", - "https://share.moroz.cc/SDAI/ONNX/universestable.zip", - "https://gh-proxy.ygxz.in/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/universestable.zip" + "https://gh.llkk.cc/https://github.com/ShiftHackZ/Local-Diffusion-Models-SDAI-ONXX/releases/download/patch-25022024/universestable.zip" ], "metadata": { "src": "https://huggingface.co/Androidonnxfork/test/tree/main/fp16fullonnxsdquantized_in_ort/othermodels1/universestable*" diff --git a/docs/motd.json b/docs/motd.json index 7b18951ea..6a0901cca 100644 --- a/docs/motd.json +++ b/docs/motd.json @@ -1,7 +1,5 @@ { - "display": true, - "title_old": "Cloud maintenance", - "subtitle_old": "SDAI cloud is now under maintenance. We are working hard to get online as soon as possible, come and check later.", - "title": "SDAI Cloud unavailable", - "subtitle": "SDAI cloud temporary unavailable due to technical maintenance, it will be available in November 2023." + "display": false, + "title": "Welcome to PDAI", + "subtitle": "Thank you for using Pocket Diffusion!" } diff --git a/docs/policy.html b/docs/policy.html index a77614aba..fa1470095 100644 --- a/docs/policy.html +++ b/docs/policy.html @@ -1,109 +1,177 @@ - - - - - Privacy Policy - - - - Privacy Policy

- Dmitriy Moroz built the Stable Diffusion AI app as - an Ad Supported app. This SERVICE is provided by - Dmitriy Moroz at no cost and is intended for use as - is. -

- This page is used to inform visitors regarding my - policies with the collection, use, and disclosure of Personal - Information if anyone decided to use my Service. -

- If you choose to use my Service, then you agree to - the collection and use of information in relation to this - policy. The Personal Information that I collect is - used for providing and improving the Service. I will not use or share your information with - anyone except as described in this Privacy Policy. -

- The terms used in this Privacy Policy have the same meanings - as in our Terms and Conditions, which are accessible at - Stable Diffusion AI unless otherwise defined in this Privacy Policy. -

Information Collection and Use

- For a better experience, while using our Service, I - may require you to provide us with certain personally - identifiable information. The information that - I request will be retained on your device and is not collected by me in any way. -

- The app does use third-party services that may collect - information used to identify you. -

- Link to the privacy policy of third-party service providers used - by the app -

Log Data

- I want to inform you that whenever you - use my Service, in a case of an error in the app - I collect data and information (through third-party - products) on your phone called Log Data. This Log Data may - include information such as your device Internet Protocol - (“IP”) address, device name, operating system version, the - configuration of the app when utilizing my Service, - the time and date of your use of the Service, and other - statistics. -

Cookies

- Cookies are files with a small amount of data that are - commonly used as anonymous unique identifiers. These are sent - to your browser from the websites that you visit and are - stored on your device's internal memory. -

- This Service does not use these “cookies” explicitly. However, - the app may use third-party code and libraries that use - “cookies” to collect information and improve their services. - You have the option to either accept or refuse these cookies - and know when a cookie is being sent to your device. If you - choose to refuse our cookies, you may not be able to use some - portions of this Service. -

Service Providers

- I may employ third-party companies and - individuals due to the following reasons: -

  • To facilitate our Service;
  • To provide the Service on our behalf;
  • To perform Service-related services; or
  • To assist us in analyzing how our Service is used.

- I want to inform users of this Service - that these third parties have access to their Personal - Information. The reason is to perform the tasks assigned to - them on our behalf. However, they are obligated not to - disclose or use the information for any other purpose. -

Security

- I value your trust in providing us your - Personal Information, thus we are striving to use commercially - acceptable means of protecting it. But remember that no method - of transmission over the internet, or method of electronic - storage is 100% secure and reliable, and I cannot - guarantee its absolute security. -

Links to Other Sites

- This Service may contain links to other sites. If you click on - a third-party link, you will be directed to that site. Note - that these external sites are not operated by me. - Therefore, I strongly advise you to review the - Privacy Policy of these websites. I have - no control over and assume no responsibility for the content, - privacy policies, or practices of any third-party sites or - services. -

Children’s Privacy

- These Services do not address anyone under the age of 13. - I do not knowingly collect personally - identifiable information from children under 13 years of age. In the case - I discover that a child under 13 has provided - me with personal information, I immediately - delete this from our servers. If you are a parent or guardian - and you are aware that your child has provided us with - personal information, please contact me so that - I will be able to do the necessary actions. -

Changes to This Privacy Policy

- I may update our Privacy Policy from - time to time. Thus, you are advised to review this page - periodically for any changes. I will - notify you of any changes by posting the new Privacy Policy on - this page. -

This policy is effective as of 2023-03-12

Contact Us

- If you have any questions or suggestions about my - Privacy Policy, do not hesitate to contact me at sdai@moroz.cc. - - - + + + + + Privacy Policy - Pocket Diffusion + + + + + + +

+ + + + + +
+ +
+ +
+
+

Privacy Policy

+ +

+ crim50n built the Pocket Diffusion app as a Free and Open Source app. + This SERVICE is provided at no cost and is intended for use as is. +

+ +

+ This page is used to inform visitors regarding policies with the collection, + use, and disclosure of Personal Information if anyone decided to use this Service. +

+ +

Information Collection and Use

+ +

+ Pocket Diffusion does not collect any personal information. All image generation + happens either on your device locally or on servers you configure yourself. + We do not have access to your prompts, images, or any other data. +

+ +

+ The app may connect to third-party servers (such as Automatic1111, ComfyUI, etc.) + that you configure. The privacy policies of those services apply to data sent to them. +

+ +

On-Device Generation

+ +

+ When using on-device generation (MediaPipe), all processing happens locally on your + Android device. No data is sent to any external servers. +

+ +

Log Data

+ +

+ The app may create local log files on your device for debugging purposes. + These logs are stored only on your device and are not transmitted anywhere. +

+ +

Third-Party Services

+ +

+ The FOSS version of the app does not include any third-party analytics or tracking services. +

+ +

Links to Other Sites

+ +

+ This Service may contain links to other sites. If you click on a third-party link, + you will be directed to that site. Note that these external sites are not operated by us. + Therefore, we strongly advise you to review the Privacy Policy of these websites. +

+ +

Children's Privacy

+ +

+ These Services do not address anyone under the age of 13. We do not knowingly collect + personally identifiable information from children under 13 years of age. +

+ +

Changes to This Privacy Policy

+ +

+ We may update our Privacy Policy from time to time. Thus, you are advised to review + this page periodically for any changes. +

+ +

This policy is effective as of 2026-01-01

+ +

Contact Us

+ +

+ If you have any questions or suggestions about this Privacy Policy, + do not hesitate to contact us via GitHub Issues. +

+
+
+ + + + + diff --git a/docs/qnn.json b/docs/qnn.json new file mode 100644 index 000000000..a3c8aa6d0 --- /dev/null +++ b/docs/qnn.json @@ -0,0 +1,712 @@ +[ + { + "id": "qnn-anything-v5-8gen2", + "name": "Anything V5 (8Gen2/3/4)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/AnythingV5_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-anything-v5-8gen1", + "name": "Anything V5 (8Gen1)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/AnythingV5_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-anything-v5-min", + "name": "Anything V5 (Other Snapdragon)", + "size": "995 MB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/AnythingV5_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-qteamix-8gen2", + "name": "QteaMix (8Gen2/3/4)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/QteaMix_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-qteamix-8gen1", + "name": "QteaMix (8Gen1)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/QteaMix_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-qteamix-min", + "name": "QteaMix (Other Snapdragon)", + "size": "995 MB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/QteaMix_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-cuteyukimix-8gen2", + "name": "CuteYukiMix (8Gen2/3/4)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/CuteYukiMix_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-cuteyukimix-8gen1", + "name": "CuteYukiMix (8Gen1)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/CuteYukiMix_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-cuteyukimix-min", + "name": "CuteYukiMix (Other Snapdragon)", + "size": "994 MB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/CuteYukiMix_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-absolutereality-8gen2", + "name": "Absolute Reality (8Gen2/3/4)", + "size": "1.05 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/AbsoluteReality_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-absolutereality-8gen1", + "name": "Absolute Reality (8Gen1)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/AbsoluteReality_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-absolutereality-min", + "name": "Absolute Reality (Other Snapdragon)", + "size": "993 MB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/AbsoluteReality_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-chilloutmix-8gen2", + "name": "ChilloutMix (8Gen2/3/4)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/ChilloutMix_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-chilloutmix-8gen1", + "name": "ChilloutMix (8Gen1)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/ChilloutMix_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-chilloutmix-min", + "name": "ChilloutMix (Other Snapdragon)", + "size": "1.01 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/ChilloutMix_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-crosskemono-8gen2", + "name": "Cross Kemono 2.5 (8Gen2/3/4)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/CrossKemono2.5_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-crosskemono-8gen1", + "name": "Cross Kemono 2.5 (8Gen1)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/CrossKemono2.5_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-crosskemono-min", + "name": "Cross Kemono 2.5 (Other Snapdragon)", + "size": "1.01 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/CrossKemono2.5_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-darksushiv4-8gen2", + "name": "Dark Sushi V4 (8Gen2/3/4)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/DarkSushiV4_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-darksushiv4-8gen1", + "name": "Dark Sushi V4 (8Gen1)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/DarkSushiV4_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-darksushiv4-min", + "name": "Dark Sushi V4 (Other Snapdragon)", + "size": "1.16 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/DarkSushiV4_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-dreamshaperv8-8gen2", + "name": "DreamShaper V8 (8Gen2/3/4)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/DreamShaperV8_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-dreamshaperv8-8gen1", + "name": "DreamShaper V8 (8Gen1)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/DreamShaperV8_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-dreamshaperv8-min", + "name": "DreamShaper V8 (Other Snapdragon)", + "size": "1.15 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/DreamShaperV8_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-hyperspirev5-8gen2", + "name": "HyperSpire V5 (8Gen2/3/4)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/HyperSpireV5_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-hyperspirev5-8gen1", + "name": "HyperSpire V5 (8Gen1)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/HyperSpireV5_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-hyperspirev5-min", + "name": "HyperSpire V5 (Other Snapdragon)", + "size": "1.01 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/HyperSpireV5_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-majicmixrealisticv7-8gen2", + "name": "MajicMix Realistic V7 (8Gen2/3/4)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MajicmixRealisticV7_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-majicmixrealisticv7-8gen1", + "name": "MajicMix Realistic V7 (8Gen1)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MajicmixRealisticV7_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-majicmixrealisticv7-min", + "name": "MajicMix Realistic V7 (Other Snapdragon)", + "size": "993 MB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MajicmixRealisticV7_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-meinamixv12-8gen2", + "name": "MeinaMix V12 (8Gen2/3/4)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MeinaMixV12_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-meinamixv12-8gen1", + "name": "MeinaMix V12 (8Gen1)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MeinaMixV12_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-meinamixv12-min", + "name": "MeinaMix V12 (Other Snapdragon)", + "size": "1.16 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MeinaMixV12_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-mistoonanimev3-8gen2", + "name": "Mistoon Anime V3 (8Gen2/3/4)", + "size": "1.05 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MistoonAnimeV3_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-mistoonanimev3-8gen1", + "name": "Mistoon Anime V3 (8Gen1)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MistoonAnimeV3_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-mistoonanimev3-min", + "name": "Mistoon Anime V3 (Other Snapdragon)", + "size": "991 MB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/MistoonAnimeV3_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-naianimev2-8gen2", + "name": "NAI Anime V2 (8Gen2/3/4)", + "size": "1.05 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/NaiAnimeV2_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-naianimev2-8gen1", + "name": "NAI Anime V2 (8Gen1)", + "size": "1.06 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/NaiAnimeV2_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-naianimev2-min", + "name": "NAI Anime V2 (Other Snapdragon)", + "size": "990 MB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/NaiAnimeV2_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-neverendingdreamv122-8gen2", + "name": "NeverEnding Dream V1.22 (8Gen2/3/4)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/NeverEndingDreamV122_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-neverendingdreamv122-8gen1", + "name": "NeverEnding Dream V1.22 (8Gen1)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/NeverEndingDreamV122_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-neverendingdreamv122-min", + "name": "NeverEnding Dream V1.22 (Other Snapdragon)", + "size": "1.16 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/NeverEndingDreamV122_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-realisianv6-8gen2", + "name": "Realisian V6 (8Gen2/3/4)", + "size": "1.04 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/RealisianV6_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-realisianv6-8gen1", + "name": "Realisian V6 (8Gen1)", + "size": "1.04 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/RealisianV6_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-realisianv6-min", + "name": "Realisian V6 (Other Snapdragon)", + "size": "1.17 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/RealisianV6_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-realisticvisionhyper-8gen2", + "name": "Realistic Vision Hyper (8Gen2/3/4)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/RealisticVisionHyper_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-realisticvisionhyper-8gen1", + "name": "Realistic Vision Hyper (8Gen1)", + "size": "1.07 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/RealisticVisionHyper_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "qnn-realisticvisionhyper-min", + "name": "Realistic Vision Hyper (Other Snapdragon)", + "size": "1.01 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/RealisticVisionHyper_qnn2.28_min.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "min", + "type": "npu" + } + }, + { + "id": "qnn-sweetmixv22flat-8gen2", + "name": "SweetMix V2.2 Flat (8Gen2/3/4)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/SweetMixV22Flat_qnn2.28_8gen2.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen2", + "type": "npu" + } + }, + { + "id": "qnn-sweetmixv22flat-8gen1", + "name": "SweetMix V2.2 Flat (8Gen1)", + "size": "1.03 GB", + "sources": [ + "https://huggingface.co/xororz/sd-qnn/resolve/main/SweetMixV22Flat_qnn2.28_8gen1.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-qnn", + "chipset": "8gen1", + "type": "npu" + } + }, + { + "id": "mnn-anything-v5", + "name": "Anything V5 (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/AnythingV5.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "cpu" + } + }, + { + "id": "mnn-qteamix", + "name": "QteaMix (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/QteaMix.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "cpu" + } + }, + { + "id": "mnn-cuteyukimix", + "name": "CuteYukiMix (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/CuteYukiMix.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "cpu" + } + }, + { + "id": "mnn-absolutereality", + "name": "Absolute Reality (CPU/MNN)", + "size": "1.19 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/AbsoluteReality.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "cpu" + } + }, + { + "id": "mnn-chilloutmix", + "name": "ChilloutMix (CPU/MNN)", + "size": "1.2 GB", + "sources": [ + "https://huggingface.co/xororz/sd-mnn/resolve/main/ChilloutMix.zip" + ], + "metadata": { + "src": "https://huggingface.co/xororz/sd-mnn", + "type": "cpu" + } + } +] diff --git a/docs/supporters.json b/docs/supporters.json index 995a87d82..fe51488c7 100644 --- a/docs/supporters.json +++ b/docs/supporters.json @@ -1,65 +1 @@ -[ - { - "id": 0, - "name": "Sören", - "date": "2023-06-27", - "amount": "10.00", - "currency": "USD", - "type": "bmc", - "message": "I like your Stable-Diffusion-Android-App! 👍" - }, - { - "id": 1, - "name": "roguelichen", - "date": "2024-02-11", - "amount": "25.00", - "currency": "USD", - "type": "bmc", - "message": "Thank you for SDAI, it's been a great deal of stimulating fun 👍" - }, - { - "id": 2, - "name": "Wyvern Dryke", - "date": "2024-06-12", - "amount": "5.00", - "currency": "USD", - "type": "bmc", - "message": "I don't have much money, but wanted to express my sincere thanks for SDAI. Thanks to you, I can keep creating art even though my illness has worsened. I can now access Automatic1111 remotely from the couch! It means so much to me, that I can keep on creating even though I can no longer sit at the desk to use my powerhouse machine. Now even my crappy tablet can do it, lol. Thank you so much! <3" - }, - { - "id": 3, - "name": "Jacob", - "date": "2024-09-02", - "amount": "5.00", - "currency": "USD", - "type": "bmc", - "message": "" - }, - { - "id": 4, - "name": "Someone", - "date": "2024-10-24", - "amount": "5.00", - "currency": "USD", - "type": "bmc", - "message": "" - }, - { - "id": 5, - "name": "Someone", - "date": "2024-11-05", - "amount": "15.00", - "currency": "USD", - "type": "bmc", - "message": "Hey shifthackz I just wanted to show my appreciation I got a little extra money and wanted to give:)" - }, - { - "id": 6, - "name": "Someone", - "date": "2024-11-22", - "amount": "5.00", - "currency": "USD", - "type": "bmc", - "message": "" - } -] +[] diff --git a/docs/version.json b/docs/version.json index fb3a46ed9..3d1811877 100644 --- a/docs/version.json +++ b/docs/version.json @@ -1,4 +1,3 @@ { - "googleplay": "0.5.3", - "fdroid": "0.4.3" -} + "github": "1.0.0" +} diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index bf96cdcdb..78bafa489 100755 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.domain" + namespace = "dev.minios.pdaiv1.domain" } dependencies { diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/GenerationResultDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/GenerationResultDataSource.kt deleted file mode 100644 index 8b28ef907..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/GenerationResultDataSource.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -sealed interface GenerationResultDataSource { - - interface Local : GenerationResultDataSource { - fun insert(result: AiGenerationResult): Single - fun queryAll(): Single> - fun queryPage(limit: Int, offset: Int): Single> - fun queryById(id: Long): Single - fun queryByIdList(idList: List): Single> - fun deleteById(id: Long): Completable - fun deleteByIdList(idList: List): Completable - fun deleteAll(): Completable - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HordeGenerationDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HordeGenerationDataSource.kt deleted file mode 100644 index 10c650b26..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HordeGenerationDataSource.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single - -sealed interface HordeGenerationDataSource { - interface Remote : HordeGenerationDataSource { - fun validateApiKey(): Single - fun textToImage(payload: TextToImagePayload): Single - fun imageToImage(payload: ImageToImagePayload): Single - fun interruptGeneration(): Completable - } - - interface StatusSource : HordeGenerationDataSource { - var id: String? - fun observe(): Flowable - fun update(status: HordeProcessStatus) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HuggingFaceGenerationDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HuggingFaceGenerationDataSource.kt deleted file mode 100644 index 36a42fa9c..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HuggingFaceGenerationDataSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -sealed interface HuggingFaceGenerationDataSource { - interface Remote : HuggingFaceGenerationDataSource { - fun validateApiKey(): Single - fun textToImage( - modelName: String, - payload: TextToImagePayload, - ): Single - - fun imageToImage( - modelName: String, - payload: ImageToImagePayload, - ): Single - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/OpenAiGenerationDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/OpenAiGenerationDataSource.kt deleted file mode 100644 index 9262c880d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/OpenAiGenerationDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -sealed interface OpenAiGenerationDataSource { - interface Remote : OpenAiGenerationDataSource { - fun validateApiKey(): Single - fun textToImage(payload: TextToImagePayload): Single - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiEnginesDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiEnginesDataSource.kt deleted file mode 100644 index 9fa2406a6..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiEnginesDataSource.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine -import io.reactivex.rxjava3.core.Single - -sealed interface StabilityAiEnginesDataSource { - interface Remote : StabilityAiGenerationDataSource { - fun fetch(): Single> - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiGenerationDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiGenerationDataSource.kt deleted file mode 100644 index 99f758d80..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiGenerationDataSource.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -sealed interface StabilityAiGenerationDataSource { - - interface Remote : StabilityAiGenerationDataSource { - - fun validateApiKey(): Single - - fun textToImage(engineId: String, payload: TextToImagePayload): Single - - fun imageToImage( - engineId: String, - payload: ImageToImagePayload, - imageBytes: ByteArray, - ): Single - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionGenerationDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionGenerationDataSource.kt deleted file mode 100755 index ffddaf23c..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionGenerationDataSource.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -sealed interface StableDiffusionGenerationDataSource { - interface Remote : StableDiffusionGenerationDataSource { - fun checkAvailability(): Completable - fun checkAvailability(url: String): Completable - fun textToImage(payload: TextToImagePayload): Single - fun imageToImage(payload: ImageToImagePayload): Single - fun interruptGeneration(): Completable - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiGenerationDataSource.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiGenerationDataSource.kt deleted file mode 100644 index 88c388a06..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiGenerationDataSource.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.domain.datasource - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -sealed interface SwarmUiGenerationDataSource { - - interface Remote : SwarmUiGenerationDataSource { - fun textToImage( - sessionId: String, - model: String, - payload: TextToImagePayload, - ): Single - - fun imageToImage( - sessionId: String, - model: String, - payload: ImageToImagePayload, - ): Single - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/demo/ImageToImageDemo.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/demo/ImageToImageDemo.kt deleted file mode 100644 index 4f7e3c946..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/demo/ImageToImageDemo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.demo - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import io.reactivex.rxjava3.core.Single - -fun interface ImageToImageDemo { - fun getDemoBase64(payload: ImageToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/demo/TextToImageDemo.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/demo/TextToImageDemo.kt deleted file mode 100644 index 73b46ae1f..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/demo/TextToImageDemo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.demo - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -fun interface TextToImageDemo { - fun getDemoBase64(payload: TextToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/di/DomainModule.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/di/DomainModule.kt deleted file mode 100755 index 3a5f59eb0..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/di/DomainModule.kt +++ /dev/null @@ -1,202 +0,0 @@ -package com.shifthackz.aisdv1.domain.di - -import com.shifthackz.aisdv1.domain.interactor.settings.SetupConnectionInterActor -import com.shifthackz.aisdv1.domain.interactor.settings.SetupConnectionInterActorImpl -import com.shifthackz.aisdv1.domain.interactor.wakelock.WakeLockInterActor -import com.shifthackz.aisdv1.domain.interactor.wakelock.WakeLockInterActorImpl -import com.shifthackz.aisdv1.domain.usecase.caching.ClearAppCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.caching.ClearAppCacheUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.caching.DataPreLoaderUseCase -import com.shifthackz.aisdv1.domain.usecase.caching.DataPreLoaderUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.caching.GetLastResultFromCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.caching.GetLastResultFromCacheUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.caching.SaveLastResultToCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.caching.SaveLastResultToCacheUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.ObserveSeverConnectivityUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.ObserveSeverConnectivityUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.PingStableDiffusionServiceUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.PingStableDiffusionServiceUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestConnectivityUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestConnectivityUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHordeApiKeyUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHordeApiKeyUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestSwarmUiConnectivityUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestSwarmUiConnectivityUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase -import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.donate.FetchAndGetSupportersUseCase -import com.shifthackz.aisdv1.domain.usecase.donate.FetchAndGetSupportersUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.downloadable.DeleteModelUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.DeleteModelUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.downloadable.DownloadModelUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.DownloadModelUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalModelUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalModelUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.downloadable.ObserveLocalOnnxModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.ObserveLocalOnnxModelsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteAllGalleryUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteAllGalleryUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemsUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.gallery.GetAllGalleryUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.GetAllGalleryUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.gallery.GetGalleryItemsUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.GetGalleryItemsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.gallery.GetMediaStoreInfoUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.GetMediaStoreInfoUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.gallery.ToggleImageVisibilityUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.ToggleImageVisibilityUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.GetRandomImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetRandomImageUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.SaveGenerationResultUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.SaveGenerationResultUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.report.SendReportUseCase -import com.shifthackz.aisdv1.domain.usecase.report.SendReportUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCase -import com.shifthackz.aisdv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCase -import com.shifthackz.aisdv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.sdlora.FetchAndGetLorasUseCase -import com.shifthackz.aisdv1.domain.usecase.sdlora.FetchAndGetLorasUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCase -import com.shifthackz.aisdv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase -import com.shifthackz.aisdv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToA1111UseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToA1111UseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHordeUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHordeUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHuggingFaceUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHuggingFaceUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToLocalDiffusionUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToLocalDiffusionUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToMediaPipeUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToMediaPipeUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToOpenAiUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToOpenAiUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToStabilityAiUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToStabilityAiUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToSwarmUiUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToSwarmUiUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.GetConfigurationUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.GetConfigurationUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.settings.SetServerConfigurationUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.SetServerConfigurationUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.splash.SplashNavigationUseCase -import com.shifthackz.aisdv1.domain.usecase.splash.SplashNavigationUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.stabilityai.FetchAndGetStabilityAiEnginesUseCase -import com.shifthackz.aisdv1.domain.usecase.stabilityai.FetchAndGetStabilityAiEnginesUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.stabilityai.ObserveStabilityAiCreditsUseCase -import com.shifthackz.aisdv1.domain.usecase.stabilityai.ObserveStabilityAiCreditsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.swarmmodel.FetchAndGetSwarmUiModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.swarmmodel.FetchAndGetSwarmUiModelsUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.wakelock.AcquireWakelockUseCase -import com.shifthackz.aisdv1.domain.usecase.wakelock.AcquireWakelockUseCaseImpl -import com.shifthackz.aisdv1.domain.usecase.wakelock.ReleaseWakeLockUseCase -import com.shifthackz.aisdv1.domain.usecase.wakelock.ReleaseWakeLockUseCaseImpl -import org.koin.core.module.dsl.factoryOf -import org.koin.dsl.bind -import org.koin.dsl.module - -internal val useCasesModule = module { - factoryOf(::TextToImageUseCaseImpl) bind TextToImageUseCase::class - factoryOf(::ImageToImageUseCaseImpl) bind ImageToImageUseCase::class - factoryOf(::PingStableDiffusionServiceUseCaseImpl) bind PingStableDiffusionServiceUseCase::class - factoryOf(::ClearAppCacheUseCaseImpl) bind ClearAppCacheUseCase::class - factoryOf(::DataPreLoaderUseCaseImpl) bind DataPreLoaderUseCase::class - factoryOf(::FetchAndGetSwarmUiModelsUseCaseImpl) bind FetchAndGetSwarmUiModelsUseCase::class - factoryOf(::GetStableDiffusionModelsUseCaseImpl) bind GetStableDiffusionModelsUseCase::class - factoryOf(::SelectStableDiffusionModelUseCaseImpl) bind SelectStableDiffusionModelUseCase::class - factoryOf(::GetGenerationResultPagedUseCaseImpl) bind GetGenerationResultPagedUseCase::class - factoryOf(::GetAllGalleryUseCaseImpl) bind GetAllGalleryUseCase::class - factoryOf(::GetGalleryItemsUseCaseImpl) bind GetGalleryItemsUseCase::class - factoryOf(::GetGenerationResultUseCaseImpl) bind GetGenerationResultUseCase::class - factoryOf(::DeleteGalleryItemUseCaseImpl) bind DeleteGalleryItemUseCase::class - factoryOf(::DeleteGalleryItemsUseCaseImpl) bind DeleteGalleryItemsUseCase::class - factoryOf(::DeleteAllGalleryUseCaseImpl) bind DeleteAllGalleryUseCase::class - factoryOf(::GetStableDiffusionSamplersUseCaseImpl) bind GetStableDiffusionSamplersUseCase::class - factoryOf(::FetchAndGetLorasUseCaseImpl) bind FetchAndGetLorasUseCase::class - factoryOf(::FetchAndGetHyperNetworksUseCaseImpl) bind FetchAndGetHyperNetworksUseCase::class - factoryOf(::FetchAndGetEmbeddingsUseCaseImpl) bind FetchAndGetEmbeddingsUseCase::class - factoryOf(::SplashNavigationUseCaseImpl) bind SplashNavigationUseCase::class - factoryOf(::GetConfigurationUseCaseImpl) bind GetConfigurationUseCase::class - factoryOf(::SetServerConfigurationUseCaseImpl) bind SetServerConfigurationUseCase::class - factoryOf(::TestConnectivityUseCaseImpl) bind TestConnectivityUseCase::class - factoryOf(::TestHordeApiKeyUseCaseImpl) bind TestHordeApiKeyUseCase::class - factoryOf(::TestHuggingFaceApiKeyUseCaseImpl) bind TestHuggingFaceApiKeyUseCase::class - factoryOf(::TestOpenAiApiKeyUseCaseImpl) bind TestOpenAiApiKeyUseCase::class - factoryOf(::TestStabilityAiApiKeyUseCaseImpl) bind TestStabilityAiApiKeyUseCase::class - factoryOf(::TestSwarmUiConnectivityUseCaseImpl) bind TestSwarmUiConnectivityUseCase::class - factoryOf(::SaveGenerationResultUseCaseImpl) bind SaveGenerationResultUseCase::class - factoryOf(::ObserveSeverConnectivityUseCaseImpl) bind ObserveSeverConnectivityUseCase::class - factoryOf(::ObserveHordeProcessStatusUseCaseImpl) bind ObserveHordeProcessStatusUseCase::class - factoryOf(::GetMediaStoreInfoUseCaseImpl) bind GetMediaStoreInfoUseCase::class - factoryOf(::ToggleImageVisibilityUseCaseImpl) bind ToggleImageVisibilityUseCase::class - factoryOf(::GetRandomImageUseCaseImpl) bind GetRandomImageUseCase::class - factoryOf(::SaveLastResultToCacheUseCaseImpl) bind SaveLastResultToCacheUseCase::class - factoryOf(::GetLastResultFromCacheUseCaseImpl) bind GetLastResultFromCacheUseCase::class - factoryOf(::ObserveLocalDiffusionProcessStatusUseCaseImpl) bind ObserveLocalDiffusionProcessStatusUseCase::class - factoryOf(::GetLocalOnnxModelsUseCaseImpl) bind GetLocalOnnxModelsUseCase::class - factoryOf(::GetLocalMediaPipeModelsUseCaseImpl) bind GetLocalMediaPipeModelsUseCase::class - factoryOf(::DownloadModelUseCaseImpl) bind DownloadModelUseCase::class - factoryOf(::ObserveLocalOnnxModelsUseCaseImpl) bind ObserveLocalOnnxModelsUseCase::class - factoryOf(::DeleteModelUseCaseImpl) bind DeleteModelUseCase::class - factoryOf(::AcquireWakelockUseCaseImpl) bind AcquireWakelockUseCase::class - factoryOf(::ReleaseWakeLockUseCaseImpl) bind ReleaseWakeLockUseCase::class - factoryOf(::InterruptGenerationUseCaseImpl) bind InterruptGenerationUseCase::class - factoryOf(::ConnectToHordeUseCaseImpl) bind ConnectToHordeUseCase::class - factoryOf(::ConnectToLocalDiffusionUseCaseImpl) bind ConnectToLocalDiffusionUseCase::class - factoryOf(::ConnectToMediaPipeUseCaseImpl) bind ConnectToMediaPipeUseCase::class - factoryOf(::ConnectToA1111UseCaseImpl) bind ConnectToA1111UseCase::class - factoryOf(::ConnectToSwarmUiUseCaseImpl) bind ConnectToSwarmUiUseCase::class - factoryOf(::ConnectToHuggingFaceUseCaseImpl) bind ConnectToHuggingFaceUseCase::class - factoryOf(::ConnectToOpenAiUseCaseImpl) bind ConnectToOpenAiUseCase::class - factoryOf(::ConnectToStabilityAiUseCaseImpl) bind ConnectToStabilityAiUseCase::class - factoryOf(::FetchAndGetHuggingFaceModelsUseCaseImpl) bind FetchAndGetHuggingFaceModelsUseCase::class - factoryOf(::ObserveStabilityAiCreditsUseCaseImpl) bind ObserveStabilityAiCreditsUseCase::class - factoryOf(::FetchAndGetStabilityAiEnginesUseCaseImpl) bind FetchAndGetStabilityAiEnginesUseCase::class - factoryOf(::FetchAndGetSupportersUseCaseImpl) bind FetchAndGetSupportersUseCase::class - factoryOf(::SendReportUseCaseImpl) bind SendReportUseCase::class - factoryOf(::GetLocalModelUseCaseImpl) bind GetLocalModelUseCase::class -} - -internal val interActorsModule = module { - factoryOf(::WakeLockInterActorImpl) bind WakeLockInterActor::class - factoryOf(::SetupConnectionInterActorImpl) bind SetupConnectionInterActor::class -} - -internal val debugModule = module { - factoryOf(::DebugInsertBadBase64UseCaseImpl) bind DebugInsertBadBase64UseCase::class -} - -val domainModule = (useCasesModule + interActorsModule + debugModule).toTypedArray() diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/BackgroundWorkStatus.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/BackgroundWorkStatus.kt deleted file mode 100644 index 4f0a28c0f..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/BackgroundWorkStatus.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class BackgroundWorkStatus( - val running: Boolean, - val statusTitle: String, - val statusSubTitle: String, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Embedding.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Embedding.kt deleted file mode 100644 index 6e2d1b90b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Embedding.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class Embedding( - val keyword: String, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Grid.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Grid.kt deleted file mode 100644 index 64b50671d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Grid.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -enum class Grid(val size: Int) { - Fixed2(2), - Fixed3(3), - Fixed4(4), - Fixed5(5); -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/HordeProcessStatus.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/HordeProcessStatus.kt deleted file mode 100644 index f64991189..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/HordeProcessStatus.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class HordeProcessStatus( - val waitTimeSeconds: Int, - val queuePosition: Int?, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LoRA.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LoRA.kt deleted file mode 100644 index 54017459e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LoRA.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class LoRA( - val name: String, - val alias: String, - val path: String, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LocalAiModel.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LocalAiModel.kt deleted file mode 100644 index b7958512e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LocalAiModel.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class LocalAiModel( - val id: String, - val type: Type, - val name: String, - val size: String, - val sources: List, - val downloaded: Boolean = false, - val selected: Boolean = false, -) { - enum class Type(val key: String) { - ONNX("onnx"), - MediaPipe("mediapipe"); - - companion object { - fun parse(value: String?) = entries.find { it.key == value } ?: ONNX - } - } - - companion object { - val CustomOnnx = LocalAiModel( - id = "CUSTOM", - type = Type.ONNX, - name = "Custom", - size = "NaN", - sources = emptyList(), - ) - - val CustomMediaPipe = LocalAiModel( - id = "CUSTOM_MP", - type = Type.MediaPipe, - name = "Custom", - size = "NaN", - sources = emptyList(), - ) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LocalDiffusionStatus.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LocalDiffusionStatus.kt deleted file mode 100644 index 779d8bc61..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/LocalDiffusionStatus.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class LocalDiffusionStatus( - val current: Int, - val total: Int, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiQuality.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiQuality.kt deleted file mode 100644 index be379024d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiQuality.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -enum class OpenAiQuality(val key: String) { - STANDARD("standard"), - HD("hd"); -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiStyle.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiStyle.kt deleted file mode 100644 index 0d85627e9..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiStyle.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -enum class OpenAiStyle(val key: String) { - VIVID("vivid"), - NATURAL("natural"); -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ServerConfiguration.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ServerConfiguration.kt deleted file mode 100755 index 1e9632c87..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ServerConfiguration.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class ServerConfiguration( - val sdModelCheckpoint: String, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiEngine.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiEngine.kt deleted file mode 100644 index f55b088df..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiEngine.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class StabilityAiEngine( - val id: String, - val name: String, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionHyperNetwork.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionHyperNetwork.kt deleted file mode 100644 index ae2e49743..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionHyperNetwork.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class StableDiffusionHyperNetwork( - val name: String, - val path: String, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionSampler.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionSampler.kt deleted file mode 100755 index 912ddd9e5..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionSampler.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -data class StableDiffusionSampler( - val name: String, - val aliases: List, - val options: Map, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/SwarmUiModel.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/SwarmUiModel.kt deleted file mode 100644 index e79e26735..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/SwarmUiModel.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -class SwarmUiModel( - val name: String, - val title: String, - val author: String, -) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/TextToImagePayload.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/TextToImagePayload.kt deleted file mode 100755 index 7ffd0bab9..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/TextToImagePayload.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.domain.entity - -import java.io.Serializable - -data class TextToImagePayload( - val prompt: String, - val negativePrompt: String, - val samplingSteps: Int, - val cfgScale: Float, - val width: Int, - val height: Int, - val restoreFaces: Boolean, - val seed: String, - val subSeed: String, - val subSeedStrength: Float, - val sampler: String, - val nsfw: Boolean, - val batchCount: Int, - val style: String?, - val quality: String?, - val openAiModel: OpenAiModel?, - val stabilityAiClipGuidance: StabilityAiClipGuidance?, - val stabilityAiStylePreset: StabilityAiStylePreset?, -) : Serializable diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/diffusion/LocalDiffusion.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/diffusion/LocalDiffusion.kt deleted file mode 100644 index afcdefd03..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/diffusion/LocalDiffusion.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.domain.feature.diffusion - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -interface LocalDiffusion { - fun process(payload: TextToImagePayload): Single - fun interrupt(): Completable - fun observeStatus(): Observable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/mediapipe/MediaPipe.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/mediapipe/MediaPipe.kt deleted file mode 100644 index d27e0502d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/mediapipe/MediaPipe.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.feature.mediapipe - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -interface MediaPipe { - fun process(payload: TextToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt deleted file mode 100644 index 78199398e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.feature.work - -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload - -interface BackgroundTaskManager { - fun scheduleTextToImageTask(payload: TextToImagePayload) - fun scheduleImageToImageTask(payload: ImageToImagePayload) - fun retryLastTextToImageTask(): Result - fun retryLastImageToImageTask(): Result - fun cancelAll(): Result -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundWorkObserver.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundWorkObserver.kt deleted file mode 100644 index bffb2577e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundWorkObserver.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.domain.feature.work - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.BackgroundWorkResult -import com.shifthackz.aisdv1.domain.entity.BackgroundWorkStatus -import io.reactivex.rxjava3.core.Flowable - -interface BackgroundWorkObserver { - fun observeStatus(): Flowable - fun observeResult(): Flowable - fun dismissResult() - fun refreshStatus() - fun postStatusMessage(title: String, subTitle: String) - fun postSuccessSignal(result: List) - fun postCancelSignal() - fun postFailedSignal(t: Throwable) - fun hasActiveTasks(): Boolean -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/MediaStoreGateway.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/MediaStoreGateway.kt deleted file mode 100644 index 4a9b7aa0b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/MediaStoreGateway.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.gateway - -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo - -interface MediaStoreGateway { - fun exportToFile(fileName: String, content: ByteArray) - fun getInfo(): MediaStoreInfo -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/ServerConnectivityGateway.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/ServerConnectivityGateway.kt deleted file mode 100644 index 807b5677e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/ServerConnectivityGateway.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.gateway - -import io.reactivex.rxjava3.core.Flowable - -fun interface ServerConnectivityGateway { - fun observe(): Flowable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/settings/SetupConnectionInterActor.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/settings/SetupConnectionInterActor.kt deleted file mode 100644 index 5959a0355..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/settings/SetupConnectionInterActor.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.domain.interactor.settings - -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToA1111UseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHordeUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHuggingFaceUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToLocalDiffusionUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToMediaPipeUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToOpenAiUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToStabilityAiUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToSwarmUiUseCase - -interface SetupConnectionInterActor { - val connectToHorde: ConnectToHordeUseCase - val connectToLocal: ConnectToLocalDiffusionUseCase - val connectToMediaPipe: ConnectToMediaPipeUseCase - val connectToA1111: ConnectToA1111UseCase - val connectToHuggingFace: ConnectToHuggingFaceUseCase - val connectToOpenAi: ConnectToOpenAiUseCase - val connectToStabilityAi: ConnectToStabilityAiUseCase - val connectToSwarmUi: ConnectToSwarmUiUseCase -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/settings/SetupConnectionInterActorImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/settings/SetupConnectionInterActorImpl.kt deleted file mode 100644 index 306da0945..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/settings/SetupConnectionInterActorImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.domain.interactor.settings - -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToA1111UseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHordeUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToHuggingFaceUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToLocalDiffusionUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToMediaPipeUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToOpenAiUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToStabilityAiUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.ConnectToSwarmUiUseCase - -internal data class SetupConnectionInterActorImpl( - override val connectToHorde: ConnectToHordeUseCase, - override val connectToLocal: ConnectToLocalDiffusionUseCase, - override val connectToMediaPipe: ConnectToMediaPipeUseCase, - override val connectToA1111: ConnectToA1111UseCase, - override val connectToHuggingFace: ConnectToHuggingFaceUseCase, - override val connectToOpenAi: ConnectToOpenAiUseCase, - override val connectToStabilityAi: ConnectToStabilityAiUseCase, - override val connectToSwarmUi: ConnectToSwarmUiUseCase, -) : SetupConnectionInterActor diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/wakelock/WakeLockInterActor.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/wakelock/WakeLockInterActor.kt deleted file mode 100644 index 3f77a7c48..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/wakelock/WakeLockInterActor.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.interactor.wakelock - -import com.shifthackz.aisdv1.domain.usecase.wakelock.AcquireWakelockUseCase -import com.shifthackz.aisdv1.domain.usecase.wakelock.ReleaseWakeLockUseCase - -interface WakeLockInterActor { - val acquireWakelockUseCase: AcquireWakelockUseCase - val releaseWakeLockUseCase: ReleaseWakeLockUseCase -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/wakelock/WakeLockInterActorImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/wakelock/WakeLockInterActorImpl.kt deleted file mode 100644 index be0d66c7d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/interactor/wakelock/WakeLockInterActorImpl.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.interactor.wakelock - -import com.shifthackz.aisdv1.domain.usecase.wakelock.AcquireWakelockUseCase -import com.shifthackz.aisdv1.domain.usecase.wakelock.ReleaseWakeLockUseCase - -internal data class WakeLockInterActorImpl( - override val acquireWakelockUseCase: AcquireWakelockUseCase, - override val releaseWakeLockUseCase: ReleaseWakeLockUseCase -) : WakeLockInterActor diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt deleted file mode 100644 index b049cf33e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.shifthackz.aisdv1.domain.preference - -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.Settings -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Flowable - -interface PreferenceManager { - var automatic1111ServerUrl: String - var swarmUiServerUrl: String - var swarmUiModel: String - var demoMode: Boolean - var developerMode: Boolean - var localMediaPipeCustomModelPath: String - var localOnnxCustomModelPath: String - var localOnnxAllowCancel: Boolean - var localOnnxSchedulerThread: SchedulersToken - var monitorConnectivity: Boolean - var autoSaveAiResults: Boolean - var saveToMediaStore: Boolean - var formAdvancedOptionsAlwaysShow: Boolean - var formPromptTaggedInput: Boolean - var source: ServerSource - var sdModel: String - var hordeApiKey: String - var openAiApiKey: String - var huggingFaceApiKey: String - var huggingFaceModel: String - var stabilityAiApiKey: String - var stabilityAiEngineId: String - var onBoardingComplete: Boolean - var forceSetupAfterUpdate: Boolean - var localOnnxModelId: String - var localOnnxUseNNAPI: Boolean - var localMediaPipeModelId: String - var designUseSystemColorPalette: Boolean - var designUseSystemDarkTheme: Boolean - var designDarkTheme: Boolean - var designColorToken: String - var designDarkThemeToken: String - var backgroundGeneration: Boolean - var backgroundProcessCount: Int - var galleryGrid: Grid - - fun observe(): Flowable - fun refresh(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/SessionPreference.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/SessionPreference.kt deleted file mode 100644 index 79aadadb8..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/SessionPreference.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.domain.preference - -interface SessionPreference { - var swarmUiSessionId: String -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/DownloadableModelRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/DownloadableModelRepository.kt deleted file mode 100644 index 9d124726e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/DownloadableModelRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -interface DownloadableModelRepository { - fun download(id: String, url: String): Observable - fun delete(id: String): Completable - fun getAllOnnx(): Single> - fun getAllMediaPipe(): Single> - fun observeAllOnnx(): Flowable> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/EmbeddingsRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/EmbeddingsRepository.kt deleted file mode 100644 index e641225ec..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/EmbeddingsRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.Embedding -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface EmbeddingsRepository { - fun fetchEmbeddings(): Completable - fun fetchAndGetEmbeddings(): Single> - fun getEmbeddings(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/GenerationResultRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/GenerationResultRepository.kt deleted file mode 100644 index de3a1aec8..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/GenerationResultRepository.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface GenerationResultRepository { - - fun getAll(): Single> - - fun getPage(limit: Int, offset: Int): Single> - - fun getMediaStoreInfo(): Single - - fun getById(id: Long): Single - - fun getByIds(idList: List): Single> - - fun insert(result: AiGenerationResult): Single - - fun deleteById(id: Long): Completable - - fun deleteByIdList(idList: List): Completable - - fun deleteAll(): Completable - - fun toggleVisibility(id: Long): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HordeGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HordeGenerationRepository.kt deleted file mode 100644 index 27f6f6eb3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HordeGenerationRepository.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single - -interface HordeGenerationRepository { - fun observeStatus(): Flowable - fun validateApiKey(): Single - fun generateFromText(payload: TextToImagePayload): Single - fun generateFromImage(payload: ImageToImagePayload): Single - fun interruptGeneration(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HuggingFaceGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HuggingFaceGenerationRepository.kt deleted file mode 100644 index 81280aa07..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HuggingFaceGenerationRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -interface HuggingFaceGenerationRepository { - fun validateApiKey(): Single - fun generateFromText(payload: TextToImagePayload): Single - fun generateFromImage(payload: ImageToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HuggingFaceModelsRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HuggingFaceModelsRepository.kt deleted file mode 100644 index a6d2c88d3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/HuggingFaceModelsRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface HuggingFaceModelsRepository { - fun fetchHuggingFaceModels(): Completable - fun fetchAndGetHuggingFaceModels(): Single> - fun getHuggingFaceModels(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/LocalDiffusionGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/LocalDiffusionGenerationRepository.kt deleted file mode 100644 index 3ed5c3709..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/LocalDiffusionGenerationRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -interface LocalDiffusionGenerationRepository { - fun observeStatus(): Observable - fun generateFromText(payload: TextToImagePayload): Single - fun interruptGeneration(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/LorasRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/LorasRepository.kt deleted file mode 100644 index 4beff1098..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/LorasRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.LoRA -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface LorasRepository { - fun fetchLoras(): Completable - fun fetchAndGetLoras(): Single> - fun getLoras(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/MediaPipeGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/MediaPipeGenerationRepository.kt deleted file mode 100644 index a7b65fb5b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/MediaPipeGenerationRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -interface MediaPipeGenerationRepository { - fun generateFromText(payload: TextToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/OpenAiGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/OpenAiGenerationRepository.kt deleted file mode 100644 index 22abdc152..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/OpenAiGenerationRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -interface OpenAiGenerationRepository { - fun validateApiKey(): Single - fun generateFromText(payload: TextToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/ReportRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/ReportRepository.kt deleted file mode 100644 index dc4ae339f..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/ReportRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.ReportReason -import io.reactivex.rxjava3.core.Completable - -interface ReportRepository { - fun send(text: String, reason: ReportReason, image: String): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiEnginesRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiEnginesRepository.kt deleted file mode 100644 index 25aba1f2c..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiEnginesRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine -import io.reactivex.rxjava3.core.Single - -interface StabilityAiEnginesRepository { - fun fetchAndGet(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiGenerationRepository.kt deleted file mode 100644 index 1de223e18..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiGenerationRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -interface StabilityAiGenerationRepository { - fun validateApiKey(): Single - fun generateFromText(payload: TextToImagePayload): Single - fun generateFromImage(payload: ImageToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionGenerationRepository.kt deleted file mode 100755 index adfaa885d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionGenerationRepository.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface StableDiffusionGenerationRepository { - fun checkApiAvailability(): Completable - fun checkApiAvailability(url: String): Completable - fun generateFromText(payload: TextToImagePayload): Single - fun generateFromImage(payload: ImageToImagePayload): Single - fun interruptGeneration(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionHyperNetworksRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionHyperNetworksRepository.kt deleted file mode 100644 index c6b01cc16..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionHyperNetworksRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface StableDiffusionHyperNetworksRepository { - fun fetchHyperNetworks(): Completable - fun fetchAndGetHyperNetworks(): Single> - fun getHyperNetworks(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionModelsRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionModelsRepository.kt deleted file mode 100755 index 6b89d0c4d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionModelsRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface StableDiffusionModelsRepository { - fun fetchModels(): Completable - fun fetchAndGetModels(): Single> - fun getModels(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionSamplersRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionSamplersRepository.kt deleted file mode 100755 index c2125f8ef..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StableDiffusionSamplersRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface StableDiffusionSamplersRepository { - fun fetchSamplers(): Completable - - fun getSamplers(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SupportersRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SupportersRepository.kt deleted file mode 100644 index 1042f7ffa..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SupportersRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.Supporter -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface SupportersRepository { - fun fetchSupporters(): Completable - fun fetchAndGetSupporters(): Single> - fun getSupporters(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SwarmUiGenerationRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SwarmUiGenerationRepository.kt deleted file mode 100644 index 824ccb34c..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SwarmUiGenerationRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface SwarmUiGenerationRepository { - fun checkApiAvailability(): Completable - fun checkApiAvailability(url: String): Completable - fun generateFromText(payload: TextToImagePayload): Single - fun generateFromImage(payload: ImageToImagePayload): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SwarmUiModelsRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SwarmUiModelsRepository.kt deleted file mode 100644 index 5e97c9ab3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/SwarmUiModelsRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -interface SwarmUiModelsRepository { - fun fetchModels(): Completable - fun fetchAndGetModels(): Single> - fun getModels(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/WakeLockRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/WakeLockRepository.kt deleted file mode 100644 index 70a4838ef..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/WakeLockRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.repository - -import android.os.PowerManager - -interface WakeLockRepository { - val wakeLock: PowerManager.WakeLock -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCase.kt deleted file mode 100644 index bce57522a..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import io.reactivex.rxjava3.core.Completable - -interface ClearAppCacheUseCase { - operator fun invoke(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCaseImpl.kt deleted file mode 100644 index 9ba9b6e30..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCaseImpl.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.FileLoggingTree -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository -import io.reactivex.rxjava3.core.Completable - -internal class ClearAppCacheUseCaseImpl( - private val fileProviderDescriptor: FileProviderDescriptor, - private val repository: GenerationResultRepository, -) : ClearAppCacheUseCase { - - override fun invoke() = Completable.concatArray( - repository.deleteAll(), - Completable.fromAction { FileLoggingTree.clearLog(fileProviderDescriptor) }, - ) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCase.kt deleted file mode 100755 index f4227a527..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import io.reactivex.rxjava3.core.Completable - -interface DataPreLoaderUseCase { - operator fun invoke(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCaseImpl.kt deleted file mode 100755 index 84a5ffe89..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCaseImpl.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import com.shifthackz.aisdv1.domain.repository.EmbeddingsRepository -import com.shifthackz.aisdv1.domain.repository.LorasRepository -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionHyperNetworksRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionModelsRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionSamplersRepository - -internal class DataPreLoaderUseCaseImpl( - private val serverConfigurationRepository: ServerConfigurationRepository, - private val sdModelsRepository: StableDiffusionModelsRepository, - private val sdSamplersRepository: StableDiffusionSamplersRepository, - private val sdLorasRepository: LorasRepository, - private val sdHyperNetworksRepository: StableDiffusionHyperNetworksRepository, - private val sdEmbeddingsRepository: EmbeddingsRepository, -) : DataPreLoaderUseCase { - - override operator fun invoke() = serverConfigurationRepository - .fetchConfiguration() - .andThen(sdModelsRepository.fetchModels()) - .andThen(sdSamplersRepository.fetchSamplers()) - .andThen(sdLorasRepository.fetchLoras()) - .andThen(sdHyperNetworksRepository.fetchHyperNetworks()) - .andThen(sdEmbeddingsRepository.fetchEmbeddings()) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCase.kt deleted file mode 100644 index bc3322b3e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Single - -interface GetLastResultFromCacheUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImpl.kt deleted file mode 100644 index 58804bf85..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import com.shifthackz.aisdv1.domain.repository.TemporaryGenerationResultRepository - -internal class GetLastResultFromCacheUseCaseImpl( - private val temporaryGenerationResultRepository: TemporaryGenerationResultRepository, -) : GetLastResultFromCacheUseCase { - - override fun invoke() = temporaryGenerationResultRepository.get() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCase.kt deleted file mode 100644 index de14fb77d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Single - -interface SaveLastResultToCacheUseCase { - operator fun invoke(result: AiGenerationResult): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImpl.kt deleted file mode 100644 index 8819b2f9e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImpl.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.TemporaryGenerationResultRepository -import io.reactivex.rxjava3.core.Single - -internal class SaveLastResultToCacheUseCaseImpl( - private val temporaryGenerationResultRepository: TemporaryGenerationResultRepository, - private val preferenceManager: PreferenceManager, -) : SaveLastResultToCacheUseCase { - - override fun invoke(result: AiGenerationResult): Single { - if (preferenceManager.autoSaveAiResults) return Single.just(result) - return temporaryGenerationResultRepository - .put(result) - .andThen(Single.just(result)) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCase.kt deleted file mode 100644 index 264cd6328..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Flowable - -interface ObserveSeverConnectivityUseCase { - operator fun invoke(): Flowable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImpl.kt deleted file mode 100644 index f01e4c26b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.gateway.ServerConnectivityGateway - -internal class ObserveSeverConnectivityUseCaseImpl( - private val serverConnectivityGateway: ServerConnectivityGateway, -) : ObserveSeverConnectivityUseCase { - - override fun invoke() = serverConnectivityGateway - .observe() - .distinctUntilChanged() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCase.kt deleted file mode 100755 index 7b406df61..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Completable - -interface PingStableDiffusionServiceUseCase { - operator fun invoke(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImpl.kt deleted file mode 100755 index 7b39af0f8..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import io.reactivex.rxjava3.core.Completable - -internal class PingStableDiffusionServiceUseCaseImpl( - private val repository: StableDiffusionGenerationRepository, -) : PingStableDiffusionServiceUseCase { - - override operator fun invoke(): Completable = repository.checkApiAvailability() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCase.kt deleted file mode 100644 index c5e50b2de..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Completable - -interface TestConnectivityUseCase { - operator fun invoke(url: String): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCaseImpl.kt deleted file mode 100644 index b8cf51bba..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository - -internal class TestConnectivityUseCaseImpl( - private val repository: StableDiffusionGenerationRepository, -) : TestConnectivityUseCase { - - override fun invoke(url: String) = repository.checkApiAvailability(url) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCase.kt deleted file mode 100644 index c2c641fce..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Single - -interface TestHordeApiKeyUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImpl.kt deleted file mode 100644 index 611c2f56b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository - -internal class TestHordeApiKeyUseCaseImpl( - private val hordeGenerationRepository: HordeGenerationRepository, -) : TestHordeApiKeyUseCase { - - override fun invoke() = hordeGenerationRepository.validateApiKey() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCase.kt deleted file mode 100644 index 2bc90e814..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Single - -interface TestHuggingFaceApiKeyUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImpl.kt deleted file mode 100644 index 683d59c3b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository - -internal class TestHuggingFaceApiKeyUseCaseImpl( - private val huggingFaceGenerationRepository: HuggingFaceGenerationRepository, -) : TestHuggingFaceApiKeyUseCase { - - override fun invoke() = huggingFaceGenerationRepository.validateApiKey() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCase.kt deleted file mode 100644 index f097e206f..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Single - -interface TestOpenAiApiKeyUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImpl.kt deleted file mode 100644 index 5ef393816..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.repository.OpenAiGenerationRepository - -internal class TestOpenAiApiKeyUseCaseImpl( - private val openAiGenerationRepository: OpenAiGenerationRepository, -) : TestOpenAiApiKeyUseCase { - - override fun invoke() = openAiGenerationRepository.validateApiKey() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCase.kt deleted file mode 100644 index 4b2e2cced..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Single - -interface TestStabilityAiApiKeyUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImpl.kt deleted file mode 100644 index 00dc2af24..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository - -internal class TestStabilityAiApiKeyUseCaseImpl( - private val stabilityAiGenerationRepository: StabilityAiGenerationRepository, -) : TestStabilityAiApiKeyUseCase { - - override fun invoke() = stabilityAiGenerationRepository.validateApiKey() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCase.kt deleted file mode 100644 index 6a29bc0d1..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import io.reactivex.rxjava3.core.Completable - -interface TestSwarmUiConnectivityUseCase { - operator fun invoke(url: String): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImpl.kt deleted file mode 100644 index e79cc0e5a..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity - -import com.shifthackz.aisdv1.domain.repository.SwarmUiGenerationRepository - -class TestSwarmUiConnectivityUseCaseImpl( - private val repository: SwarmUiGenerationRepository, -) : TestSwarmUiConnectivityUseCase { - - override fun invoke(url: String) = repository.checkApiAvailability(url) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCase.kt deleted file mode 100644 index 8dfba92b2..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.debug - -import io.reactivex.rxjava3.core.Completable - -interface DebugInsertBadBase64UseCase { - operator fun invoke(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCase.kt deleted file mode 100644 index c724d96d5..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.donate - -import com.shifthackz.aisdv1.domain.entity.Supporter -import io.reactivex.rxjava3.core.Single - -interface FetchAndGetSupportersUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImpl.kt deleted file mode 100644 index 28a81f7ea..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.donate - -import com.shifthackz.aisdv1.domain.entity.Supporter -import com.shifthackz.aisdv1.domain.repository.SupportersRepository -import io.reactivex.rxjava3.core.Single - -class FetchAndGetSupportersUseCaseImpl( - private val supportersRepository: SupportersRepository, -) : FetchAndGetSupportersUseCase { - - override fun invoke(): Single> = supportersRepository.fetchAndGetSupporters() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCase.kt deleted file mode 100644 index 9fc4e7c6e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import io.reactivex.rxjava3.core.Completable - -interface DeleteModelUseCase { - operator fun invoke(id: String): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCaseImpl.kt deleted file mode 100644 index b35a0043b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository - -internal class DeleteModelUseCaseImpl( - private val downloadableModelRepository: DownloadableModelRepository, -) : DeleteModelUseCase { - - override fun invoke(id: String) = downloadableModelRepository.delete(id) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCase.kt deleted file mode 100644 index a956f3c6c..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.entity.DownloadState -import io.reactivex.rxjava3.core.Observable - -interface DownloadModelUseCase { - operator fun invoke(id: String, url: String): Observable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt deleted file mode 100644 index d9d326332..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository - -internal class DownloadModelUseCaseImpl( - private val downloadableModelRepository: DownloadableModelRepository, -) : DownloadModelUseCase { - - override fun invoke(id: String, url: String) = downloadableModelRepository.download(id, url) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCase.kt deleted file mode 100644 index cd93801fd..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import io.reactivex.rxjava3.core.Single - -interface GetLocalMediaPipeModelsUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImpl.kt deleted file mode 100644 index 4da8eeba6..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository - -internal class GetLocalMediaPipeModelsUseCaseImpl( - private val downloadableModelRepository: DownloadableModelRepository, - ) : GetLocalMediaPipeModelsUseCase { - - override fun invoke() = downloadableModelRepository.getAllMediaPipe() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCase.kt deleted file mode 100644 index 4bf4b43d3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import io.reactivex.rxjava3.core.Single - -interface GetLocalModelUseCase { - operator fun invoke(id: String): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt deleted file mode 100644 index 3b6c6188f..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import io.reactivex.rxjava3.core.Single - -internal class GetLocalModelUseCaseImpl( - private val localDataSource: DownloadableModelDataSource.Local, -) : GetLocalModelUseCase { - - override fun invoke(id: String): Single = localDataSource.getById(id) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCase.kt deleted file mode 100644 index f2cce297e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import io.reactivex.rxjava3.core.Single - -interface GetLocalOnnxModelsUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImpl.kt deleted file mode 100644 index 444eccca0..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository - -internal class GetLocalOnnxModelsUseCaseImpl( - private val downloadableModelRepository: DownloadableModelRepository, -) : GetLocalOnnxModelsUseCase { - - override fun invoke() = downloadableModelRepository.getAllOnnx() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCase.kt deleted file mode 100644 index 021ebd1e2..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import io.reactivex.rxjava3.core.Flowable - -interface ObserveLocalOnnxModelsUseCase { - operator fun invoke(): Flowable> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImpl.kt deleted file mode 100644 index e5e290f81..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable - -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository - -internal class ObserveLocalOnnxModelsUseCaseImpl( - private val repository: DownloadableModelRepository, -) : ObserveLocalOnnxModelsUseCase { - - override fun invoke() = repository - .observeAllOnnx() - .distinctUntilChanged() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCase.kt deleted file mode 100644 index 7b50772ee..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import io.reactivex.rxjava3.core.Completable - -interface DeleteAllGalleryUseCase { - operator fun invoke(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImpl.kt deleted file mode 100644 index b0046b803..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository -import io.reactivex.rxjava3.core.Completable - -internal class DeleteAllGalleryUseCaseImpl( - private val generationResultRepository: GenerationResultRepository, -) : DeleteAllGalleryUseCase { - - override fun invoke(): Completable = generationResultRepository.deleteAll() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCase.kt deleted file mode 100644 index 7d003bce3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import io.reactivex.rxjava3.core.Completable - -interface DeleteGalleryItemUseCase { - operator fun invoke(id: Long): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImpl.kt deleted file mode 100644 index a57c60900..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository -import io.reactivex.rxjava3.core.Completable - -internal class DeleteGalleryItemUseCaseImpl( - private val repository: GenerationResultRepository, -) : DeleteGalleryItemUseCase { - - override fun invoke(id: Long): Completable = repository.deleteById(id) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCase.kt deleted file mode 100644 index 98e77acac..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import io.reactivex.rxjava3.core.Completable - -interface DeleteGalleryItemsUseCase { - operator fun invoke(ids: List): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImpl.kt deleted file mode 100644 index 4652b637b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository -import io.reactivex.rxjava3.core.Completable - -internal class DeleteGalleryItemsUseCaseImpl( - private val generationResultRepository: GenerationResultRepository, -) : DeleteGalleryItemsUseCase { - - override fun invoke(ids: List): Completable = generationResultRepository.deleteByIdList(ids) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCase.kt deleted file mode 100644 index e09d6a0c6..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Single - -interface GetAllGalleryUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCaseImpl.kt deleted file mode 100644 index 1174866d3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository - -internal class GetAllGalleryUseCaseImpl( - private val repository: GenerationResultRepository, -) : GetAllGalleryUseCase { - - override operator fun invoke() = repository.getAll() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetGalleryItemsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetGalleryItemsUseCase.kt deleted file mode 100644 index 7dde0a363..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetGalleryItemsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Single - -interface GetGalleryItemsUseCase { - operator fun invoke(ids: List): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetGalleryItemsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetGalleryItemsUseCaseImpl.kt deleted file mode 100644 index c441b9b43..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetGalleryItemsUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository - -internal class GetGalleryItemsUseCaseImpl( - private val generationResultRepository: GenerationResultRepository, -) : GetGalleryItemsUseCase { - - override fun invoke(ids: List) = generationResultRepository.getByIds(ids) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCase.kt deleted file mode 100644 index af831df9f..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import io.reactivex.rxjava3.core.Single - -interface GetMediaStoreInfoUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImpl.kt deleted file mode 100644 index 498366bb3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository - -class GetMediaStoreInfoUseCaseImpl( - private val repository: GenerationResultRepository, -) : GetMediaStoreInfoUseCase { - - override fun invoke() = repository.getMediaStoreInfo() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt deleted file mode 100644 index fe8f5410d..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import io.reactivex.rxjava3.core.Single - -interface ToggleImageVisibilityUseCase { - operator fun invoke(id: Long): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt deleted file mode 100644 index f7310f9fd..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository - -internal class ToggleImageVisibilityUseCaseImpl( - private val repository: GenerationResultRepository, -) : ToggleImageVisibilityUseCase { - - override fun invoke(id: Long) = repository.toggleVisibility(id) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCase.kt deleted file mode 100644 index a77873357..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Single - -interface GetGenerationResultPagedUseCase { - operator fun invoke(limit: Int, offset: Int): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImpl.kt deleted file mode 100644 index 4ca7b5c50..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository - -internal class GetGenerationResultPagedUseCaseImpl( - private val repository: GenerationResultRepository, -) : GetGenerationResultPagedUseCase { - - override operator fun invoke(limit: Int, offset: Int) = repository.getPage(limit, offset) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCase.kt deleted file mode 100644 index bc705d18f..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Single - -interface GetGenerationResultUseCase { - operator fun invoke(id: Long): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCaseImpl.kt deleted file mode 100644 index 36965cbfb..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository - -internal class GetGenerationResultUseCaseImpl( - private val repository: GenerationResultRepository, -) : GetGenerationResultUseCase { - - override operator fun invoke(id: Long) = repository.getById(id) -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCase.kt deleted file mode 100644 index 5f4b58eb9..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import android.graphics.Bitmap -import io.reactivex.rxjava3.core.Single - -interface GetRandomImageUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCaseImpl.kt deleted file mode 100644 index 439834c25..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.repository.RandomImageRepository - -class GetRandomImageUseCaseImpl( - private val randomImageRepository: RandomImageRepository, -) : GetRandomImageUseCase { - - override fun invoke() = randomImageRepository.fetchAndGet() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCase.kt deleted file mode 100644 index c3d1c02d9..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCase.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import io.reactivex.rxjava3.core.Single - -interface ImageToImageUseCase { - operator fun invoke(payload: ImageToImagePayload): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCaseImpl.kt deleted file mode 100644 index fcade672e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCaseImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.SwarmUiGenerationRepository -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -internal class ImageToImageUseCaseImpl( - private val stableDiffusionGenerationRepository: StableDiffusionGenerationRepository, - private val swarmUiGenerationRepository: SwarmUiGenerationRepository, - private val hordeGenerationRepository: HordeGenerationRepository, - private val huggingFaceGenerationRepository: HuggingFaceGenerationRepository, - private val stabilityAiGenerationRepository: StabilityAiGenerationRepository, - private val preferenceManager: PreferenceManager, -) : ImageToImageUseCase { - - override fun invoke(payload: ImageToImagePayload) = Observable - .range(1, payload.batchCount) - .flatMapSingle { generate(payload) } - .toList() - - private fun generate(payload: ImageToImagePayload) = when (preferenceManager.source) { - ServerSource.AUTOMATIC1111 -> stableDiffusionGenerationRepository.generateFromImage(payload) - ServerSource.SWARM_UI -> swarmUiGenerationRepository.generateFromImage(payload) - ServerSource.HORDE -> hordeGenerationRepository.generateFromImage(payload) - ServerSource.HUGGING_FACE -> huggingFaceGenerationRepository.generateFromImage(payload) - ServerSource.STABILITY_AI -> stabilityAiGenerationRepository.generateFromImage(payload) - else -> Single.error(IllegalStateException("Img2Img not yet supported on ${preferenceManager.source}!")) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCase.kt deleted file mode 100644 index 267dd45c8..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import io.reactivex.rxjava3.core.Completable - -interface InterruptGenerationUseCase { - operator fun invoke(): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCaseImpl.kt deleted file mode 100644 index 37f6720cd..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCaseImpl.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import io.reactivex.rxjava3.core.Completable - -internal class InterruptGenerationUseCaseImpl( - private val stableDiffusionGenerationRepository: StableDiffusionGenerationRepository, - private val hordeGenerationRepository: HordeGenerationRepository, - private val localDiffusionGenerationRepository: LocalDiffusionGenerationRepository, - private val preferenceManager: PreferenceManager, -) : InterruptGenerationUseCase { - - override fun invoke() = when (preferenceManager.source) { - ServerSource.AUTOMATIC1111 -> stableDiffusionGenerationRepository.interruptGeneration() - ServerSource.HORDE -> hordeGenerationRepository.interruptGeneration() - ServerSource.LOCAL_MICROSOFT_ONNX -> localDiffusionGenerationRepository.interruptGeneration() - else -> Completable.complete() - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCase.kt deleted file mode 100644 index e43324368..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import io.reactivex.rxjava3.core.Flowable - -interface ObserveHordeProcessStatusUseCase { - operator fun invoke(): Flowable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImpl.kt deleted file mode 100644 index c7154bedc..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository - -internal class ObserveHordeProcessStatusUseCaseImpl( - private val hordeGenerationRepository: HordeGenerationRepository, -) : ObserveHordeProcessStatusUseCase { - - override fun invoke() = hordeGenerationRepository - .observeStatus() - .distinctUntilChanged() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCase.kt deleted file mode 100644 index 9530b6b09..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import io.reactivex.rxjava3.core.Observable - -interface ObserveLocalDiffusionProcessStatusUseCase { - operator fun invoke(): Observable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImpl.kt deleted file mode 100644 index 8e8430ddc..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository - -internal class ObserveLocalDiffusionProcessStatusUseCaseImpl( - private val localDiffusionGenerationRepository: LocalDiffusionGenerationRepository, -) : ObserveLocalDiffusionProcessStatusUseCase { - - override fun invoke() = localDiffusionGenerationRepository - .observeStatus() - .distinctUntilChanged() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCase.kt deleted file mode 100644 index 588d5547e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.Completable - -interface SaveGenerationResultUseCase { - operator fun invoke(result: AiGenerationResult): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCaseImpl.kt deleted file mode 100644 index 9af8e269e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCaseImpl.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository - -internal class SaveGenerationResultUseCaseImpl( - private val repository: GenerationResultRepository, -) : SaveGenerationResultUseCase { - - override fun invoke(result: AiGenerationResult) = repository - .insert(result) - .ignoreElement() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCase.kt deleted file mode 100755 index 2dfe4f24b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCase.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import io.reactivex.rxjava3.core.Single - -interface TextToImageUseCase { - operator fun invoke(payload: TextToImagePayload): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCaseImpl.kt deleted file mode 100755 index f823a153c..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCaseImpl.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.generation - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.MediaPipeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.OpenAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.SwarmUiGenerationRepository -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -internal class TextToImageUseCaseImpl( - private val stableDiffusionGenerationRepository: StableDiffusionGenerationRepository, - private val hordeGenerationRepository: HordeGenerationRepository, - private val huggingFaceGenerationRepository: HuggingFaceGenerationRepository, - private val openAiGenerationRepository: OpenAiGenerationRepository, - private val stabilityAiGenerationRepository: StabilityAiGenerationRepository, - private val swarmUiGenerationRepository: SwarmUiGenerationRepository, - private val localDiffusionGenerationRepository: LocalDiffusionGenerationRepository, - private val mediaPipeGenerationRepository: MediaPipeGenerationRepository, - private val preferenceManager: PreferenceManager, -) : TextToImageUseCase { - - override operator fun invoke( - payload: TextToImagePayload, - ): Single> = Observable - .range(1, payload.batchCount) - .flatMapSingle { generate(payload) } - .toList() - - private fun generate(payload: TextToImagePayload) = when (preferenceManager.source) { - ServerSource.HORDE -> hordeGenerationRepository.generateFromText(payload) - ServerSource.LOCAL_MICROSOFT_ONNX -> localDiffusionGenerationRepository.generateFromText(payload) - ServerSource.HUGGING_FACE -> huggingFaceGenerationRepository.generateFromText(payload) - ServerSource.AUTOMATIC1111 -> stableDiffusionGenerationRepository.generateFromText(payload) - ServerSource.OPEN_AI -> openAiGenerationRepository.generateFromText(payload) - ServerSource.STABILITY_AI -> stabilityAiGenerationRepository.generateFromText(payload) - ServerSource.SWARM_UI -> swarmUiGenerationRepository.generateFromText(payload) - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> mediaPipeGenerationRepository.generateFromText(payload) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCase.kt deleted file mode 100644 index adb71b999..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.huggingface - -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import io.reactivex.rxjava3.core.Single - -interface FetchAndGetHuggingFaceModelsUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImpl.kt deleted file mode 100644 index e861b6a2b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.huggingface - -import com.shifthackz.aisdv1.domain.repository.HuggingFaceModelsRepository - -internal class FetchAndGetHuggingFaceModelsUseCaseImpl( - private val huggingFaceModelsRepository: HuggingFaceModelsRepository, -) : FetchAndGetHuggingFaceModelsUseCase { - - override fun invoke() = huggingFaceModelsRepository.fetchAndGetHuggingFaceModels() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/report/SendReportUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/report/SendReportUseCase.kt deleted file mode 100644 index 1e4cae100..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/report/SendReportUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.report - -import com.shifthackz.aisdv1.domain.entity.ReportReason -import io.reactivex.rxjava3.core.Completable - -interface SendReportUseCase { - operator fun invoke(text: String, reason: ReportReason, image: String): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/report/SendReportUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/report/SendReportUseCaseImpl.kt deleted file mode 100644 index 301e905b6..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/report/SendReportUseCaseImpl.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.report - -import com.shifthackz.aisdv1.domain.entity.ReportReason -import com.shifthackz.aisdv1.domain.repository.ReportRepository - -class SendReportUseCaseImpl( - private val repository: ReportRepository, -) : SendReportUseCase { - - override fun invoke(text: String, reason: ReportReason, image: String) = - repository.send(text, reason, image) - -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCase.kt deleted file mode 100644 index ca7319286..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdembedding - -import com.shifthackz.aisdv1.domain.entity.Embedding -import io.reactivex.rxjava3.core.Single - -interface FetchAndGetEmbeddingsUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImpl.kt deleted file mode 100644 index 257e37e08..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdembedding - -import com.shifthackz.aisdv1.domain.entity.Embedding -import com.shifthackz.aisdv1.domain.repository.EmbeddingsRepository -import io.reactivex.rxjava3.core.Single - -internal class FetchAndGetEmbeddingsUseCaseImpl( - private val repository: EmbeddingsRepository, -) : FetchAndGetEmbeddingsUseCase { - - override fun invoke(): Single> = repository.fetchAndGetEmbeddings() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCase.kt deleted file mode 100644 index 1215b97ef..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdhypernet - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork -import io.reactivex.rxjava3.core.Single - -interface FetchAndGetHyperNetworksUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImpl.kt deleted file mode 100644 index daa65aa92..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImpl.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdhypernet - -import com.shifthackz.aisdv1.domain.repository.StableDiffusionHyperNetworksRepository - -internal class FetchAndGetHyperNetworksUseCaseImpl( - private val stableDiffusionHyperNetworksRepository: StableDiffusionHyperNetworksRepository, -) : FetchAndGetHyperNetworksUseCase { - override fun invoke() = stableDiffusionHyperNetworksRepository.fetchAndGetHyperNetworks() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCase.kt deleted file mode 100644 index 65936385e..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdlora - -import com.shifthackz.aisdv1.domain.entity.LoRA -import io.reactivex.rxjava3.core.Single - -interface FetchAndGetLorasUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImpl.kt deleted file mode 100644 index fd9bc42f0..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdlora - -import com.shifthackz.aisdv1.domain.repository.LorasRepository - -internal class FetchAndGetLorasUseCaseImpl( - private val lorasRepository: LorasRepository, -) : FetchAndGetLorasUseCase { - - override fun invoke() = lorasRepository.fetchAndGetLoras() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCase.kt deleted file mode 100644 index a03c0b298..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdmodel - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel -import io.reactivex.rxjava3.core.Single - -interface GetStableDiffusionModelsUseCase { - operator fun invoke(): Single>> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImpl.kt deleted file mode 100644 index 3d7443d72..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImpl.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdmodel - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionModelsRepository -import io.reactivex.rxjava3.core.Single - -internal class GetStableDiffusionModelsUseCaseImpl( - private val serverConfigurationRepository: ServerConfigurationRepository, - private val sdModelsRepository: StableDiffusionModelsRepository, -) : GetStableDiffusionModelsUseCase { - - override operator fun invoke(): Single>> = - serverConfigurationRepository - .fetchAndGetConfiguration() - .flatMap { config -> - sdModelsRepository - .fetchAndGetModels() - .map { sdModels -> config to sdModels } - } - .map { (config, sdModels) -> - sdModels.map { model -> - model to (config.sdModelCheckpoint == model.title) - } - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCase.kt deleted file mode 100644 index cdf9ccb81..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdmodel - -import io.reactivex.rxjava3.core.Completable - -interface SelectStableDiffusionModelUseCase { - operator fun invoke(modelName: String): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCase.kt deleted file mode 100644 index 2a4d68c2b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdsampler - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import io.reactivex.rxjava3.core.Single - -interface GetStableDiffusionSamplersUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImpl.kt deleted file mode 100644 index 6a0ff21f2..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImpl.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdsampler - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import com.shifthackz.aisdv1.domain.repository.StableDiffusionSamplersRepository -import io.reactivex.rxjava3.core.Single - -internal class GetStableDiffusionSamplersUseCaseImpl( - private val repository: StableDiffusionSamplersRepository, -) : GetStableDiffusionSamplersUseCase { - - override operator fun invoke(): Single> = repository - .getSamplers() -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCase.kt deleted file mode 100644 index 28bcb057b..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import io.reactivex.rxjava3.core.Single - -interface ConnectToA1111UseCase { - operator fun invoke( - url: String, - isDemo: Boolean, - credentials: AuthorizationCredentials, - ): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCase.kt deleted file mode 100644 index ebe7a15da..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import io.reactivex.rxjava3.core.Single - -interface ConnectToHordeUseCase { - operator fun invoke(apiKey: String): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCase.kt deleted file mode 100644 index f15a207e1..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import io.reactivex.rxjava3.core.Single - -interface ConnectToHuggingFaceUseCase { - operator fun invoke(apiKey: String, model: String): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCase.kt deleted file mode 100644 index 31c7d1faa..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import io.reactivex.rxjava3.core.Single - -interface ConnectToLocalDiffusionUseCase { - operator fun invoke(modelId: String): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToMediaPipeUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToMediaPipeUseCase.kt deleted file mode 100644 index 5c1027e67..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToMediaPipeUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import io.reactivex.rxjava3.core.Single - -interface ConnectToMediaPipeUseCase { - operator fun invoke(modelId: String): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCase.kt deleted file mode 100644 index 7a2c645a6..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import io.reactivex.rxjava3.core.Single - -interface ConnectToOpenAiUseCase { - operator fun invoke(apiKey: String): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCase.kt deleted file mode 100644 index ff9565f01..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import io.reactivex.rxjava3.core.Single - -interface ConnectToStabilityAiUseCase { - operator fun invoke(apiKey: String): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToSwarmUiUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToSwarmUiUseCase.kt deleted file mode 100644 index 7d77f2a65..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToSwarmUiUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import io.reactivex.rxjava3.core.Single - -interface ConnectToSwarmUiUseCase { - operator fun invoke(url: String, credentials: AuthorizationCredentials): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCase.kt deleted file mode 100644 index b7540c0e2..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import com.shifthackz.aisdv1.domain.entity.Configuration -import io.reactivex.rxjava3.core.Single - -interface GetConfigurationUseCase { - operator fun invoke(): Single -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCase.kt deleted file mode 100644 index 1e9ce6f57..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.settings - -import com.shifthackz.aisdv1.domain.entity.Configuration -import io.reactivex.rxjava3.core.Completable - -interface SetServerConfigurationUseCase { - operator fun invoke(configuration: Configuration): Completable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCase.kt deleted file mode 100644 index 254da1d85..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.stabilityai - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine -import io.reactivex.rxjava3.core.Single - -interface FetchAndGetStabilityAiEnginesUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImpl.kt deleted file mode 100644 index 5a17f6f67..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.stabilityai - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.StabilityAiEnginesRepository -import io.reactivex.rxjava3.core.Single - -internal class FetchAndGetStabilityAiEnginesUseCaseImpl( - private val repository: StabilityAiEnginesRepository, - private val preferenceManager: PreferenceManager, -) : FetchAndGetStabilityAiEnginesUseCase { - - override fun invoke() = repository - .fetchAndGet() - .flatMap { engines -> - if (!engines.map(StabilityAiEngine::id).contains(preferenceManager.stabilityAiEngineId)) { - preferenceManager.stabilityAiEngineId = engines.firstOrNull()?.id ?: "" - } - Single.just(engines) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCase.kt deleted file mode 100644 index 6b09390e4..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.stabilityai - -import io.reactivex.rxjava3.core.Flowable - -interface ObserveStabilityAiCreditsUseCase { - operator fun invoke(): Flowable -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImpl.kt deleted file mode 100644 index a2764880a..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImpl.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.stabilityai - -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.StabilityAiCreditsRepository -import io.reactivex.rxjava3.core.Flowable - -internal class ObserveStabilityAiCreditsUseCaseImpl( - private val repository: StabilityAiCreditsRepository, - private val preferenceManager: PreferenceManager, -) : ObserveStabilityAiCreditsUseCase { - - override fun invoke() = Flowable - .combineLatest( - preferenceManager.observe().map(Settings::source), - repository.fetchAndObserve(), - ::Pair, - ) - .map(Pair::second) - .onErrorReturn { 0f } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCase.kt deleted file mode 100644 index 35b555fbd..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.swarmmodel - -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import io.reactivex.rxjava3.core.Single - -interface FetchAndGetSwarmUiModelsUseCase { - operator fun invoke(): Single> -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImpl.kt deleted file mode 100644 index 556067ab3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.swarmmodel - -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.SwarmUiModelsRepository -import io.reactivex.rxjava3.core.Single - -internal class FetchAndGetSwarmUiModelsUseCaseImpl( - private val preferenceManager: PreferenceManager, - private val repository: SwarmUiModelsRepository, -) : FetchAndGetSwarmUiModelsUseCase { - - override fun invoke(): Single> = repository - .fetchAndGetModels() - .map { models -> - if (!models.map(SwarmUiModel::name).contains(preferenceManager.swarmUiModel)) { - preferenceManager.swarmUiModel = models.firstOrNull()?.name ?: "" - } - models - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCaseImpl.kt deleted file mode 100644 index 57f91a548..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCaseImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.wakelock - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.domain.repository.WakeLockRepository - -internal class AcquireWakelockUseCaseImpl( - private val wakeLockRepository: WakeLockRepository, -) : AcquireWakelockUseCase { - - override fun invoke(timeout: Long) = runCatching { - wakeLockRepository.wakeLock.acquire(timeout) - }.onFailure { t -> - errorLog(t) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCase.kt deleted file mode 100644 index 42281feca..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCase.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.wakelock - -interface ReleaseWakeLockUseCase { - operator fun invoke(): Result -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImpl.kt deleted file mode 100644 index adb7473f3..000000000 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.wakelock - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.domain.repository.WakeLockRepository - -internal class ReleaseWakeLockUseCaseImpl( - private val wakeLockRepository: WakeLockRepository, -) : ReleaseWakeLockUseCase { - - override fun invoke() = runCatching { - wakeLockRepository.wakeLock.release() - }.onFailure { t -> - errorLog(t) - } -} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/DownloadableModelDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/DownloadableModelDataSource.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/DownloadableModelDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/DownloadableModelDataSource.kt index cb56c6215..a4c380d80 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/DownloadableModelDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/DownloadableModelDataSource.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable @@ -17,6 +17,7 @@ sealed interface DownloadableModelDataSource { interface Local : DownloadableModelDataSource { fun getAllOnnx(): Single> fun getAllMediaPipe(): Single> + fun getAllQnn(): Single> fun getById(id: String): Single fun getSelectedOnnx(): Single fun observeAllOnnx(): Flowable> diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/EmbeddingsDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/EmbeddingsDataSource.kt similarity index 85% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/EmbeddingsDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/EmbeddingsDataSource.kt index cadd6b43b..4712a990e 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/EmbeddingsDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/EmbeddingsDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.Embedding +import dev.minios.pdaiv1.domain.entity.Embedding import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/FalAiEndpointDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/FalAiEndpointDataSource.kt new file mode 100644 index 000000000..16c65f34b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/FalAiEndpointDataSource.kt @@ -0,0 +1,34 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +interface FalAiEndpointDataSource { + + /** + * Provides built-in endpoints shipped with the app. + */ + interface BuiltIn { + fun getAll(): Single> + } + + /** + * Provides remote endpoint fetching from URL. + */ + interface Remote { + fun fetchFromUrl(url: String): Single + } + + /** + * Provides local storage for custom endpoints. + */ + interface Local { + fun observeAll(): Observable> + fun getAll(): Single> + fun getById(id: String): Single + fun save(endpoint: FalAiEndpoint): Completable + fun delete(id: String): Completable + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/FalAiGenerationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/FalAiGenerationDataSource.kt new file mode 100644 index 000000000..013be7e24 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/FalAiGenerationDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +sealed interface FalAiGenerationDataSource { + + interface Remote : FalAiGenerationDataSource { + + fun validateApiKey(): Single + + fun textToImage(model: String, payload: TextToImagePayload): Single + + fun generateDynamic( + endpoint: FalAiEndpoint, + parameters: Map, + ): Single> + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/GenerationResultDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/GenerationResultDataSource.kt new file mode 100644 index 000000000..ddf94cdcd --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/GenerationResultDataSource.kt @@ -0,0 +1,28 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ThumbnailData +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +sealed interface GenerationResultDataSource { + + interface Local : GenerationResultDataSource { + fun insert(result: AiGenerationResult): Single + fun queryAll(): Single> + fun queryAllIds(): Single> + fun queryAllIdsWithBlurHash(): Single>> + fun queryThumbnailInfoByIdList(idList: List): Single> + fun queryPage(limit: Int, offset: Int): Single> + fun queryById(id: Long): Single + fun queryByIdList(idList: List): Single> + fun deleteById(id: Long): Completable + fun deleteByIdList(idList: List): Completable + fun deleteAll(): Completable + fun deleteAllUnliked(): Completable + fun likeByIds(idList: List): Completable + fun unlikeByIds(idList: List): Completable + fun hideByIds(idList: List): Completable + fun unhideByIds(idList: List): Completable + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HordeGenerationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HordeGenerationDataSource.kt new file mode 100644 index 000000000..a29a4c25a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HordeGenerationDataSource.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single + +sealed interface HordeGenerationDataSource { + interface Remote : HordeGenerationDataSource { + fun validateApiKey(): Single + fun textToImage(payload: TextToImagePayload): Single + fun imageToImage(payload: ImageToImagePayload): Single + fun interruptGeneration(): Completable + } + + interface StatusSource : HordeGenerationDataSource { + var id: String? + fun observe(): Flowable + fun update(status: HordeProcessStatus) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HuggingFaceGenerationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HuggingFaceGenerationDataSource.kt new file mode 100644 index 000000000..f82d3050a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HuggingFaceGenerationDataSource.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +sealed interface HuggingFaceGenerationDataSource { + interface Remote : HuggingFaceGenerationDataSource { + fun validateApiKey(): Single + fun textToImage( + modelName: String, + payload: TextToImagePayload, + ): Single + + fun imageToImage( + modelName: String, + payload: ImageToImagePayload, + ): Single + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HuggingFaceModelsDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HuggingFaceModelsDataSource.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HuggingFaceModelsDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HuggingFaceModelsDataSource.kt index fe4ca05ab..d1fcb424b 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/HuggingFaceModelsDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/HuggingFaceModelsDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/LorasDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/LorasDataSource.kt similarity index 84% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/LorasDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/LorasDataSource.kt index fd61ed0fb..d44204010 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/LorasDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/LorasDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.LoRA +import dev.minios.pdaiv1.domain.entity.LoRA import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/OpenAiGenerationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/OpenAiGenerationDataSource.kt new file mode 100644 index 000000000..100859033 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/OpenAiGenerationDataSource.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +sealed interface OpenAiGenerationDataSource { + interface Remote : OpenAiGenerationDataSource { + fun validateApiKey(): Single + fun textToImage(payload: TextToImagePayload): Single + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/RandomImageDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/RandomImageDataSource.kt similarity index 81% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/RandomImageDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/RandomImageDataSource.kt index 16ad2fc72..a739ee03d 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/RandomImageDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/RandomImageDataSource.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource import android.graphics.Bitmap import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/ReportDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/ReportDataSource.kt similarity index 75% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/ReportDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/ReportDataSource.kt index 7547588db..5f892c631 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/ReportDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/ReportDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.ReportReason +import dev.minios.pdaiv1.domain.entity.ReportReason import io.reactivex.rxjava3.core.Completable sealed interface ReportDataSource { diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/ServerConfigurationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/ServerConfigurationDataSource.kt similarity index 82% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/ServerConfigurationDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/ServerConfigurationDataSource.kt index 09b0698e1..741528253 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/ServerConfigurationDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/ServerConfigurationDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.domain.entity.ServerConfiguration import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiCreditsDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiCreditsDataSource.kt similarity index 90% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiCreditsDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiCreditsDataSource.kt index f12cd6391..ca96cb18e 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StabilityAiCreditsDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiCreditsDataSource.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiEnginesDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiEnginesDataSource.kt new file mode 100644 index 000000000..0d4af4ffc --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiEnginesDataSource.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine +import io.reactivex.rxjava3.core.Single + +sealed interface StabilityAiEnginesDataSource { + interface Remote : StabilityAiGenerationDataSource { + fun fetch(): Single> + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiGenerationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiGenerationDataSource.kt new file mode 100644 index 000000000..160e7c76b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StabilityAiGenerationDataSource.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +sealed interface StabilityAiGenerationDataSource { + + interface Remote : StabilityAiGenerationDataSource { + + fun validateApiKey(): Single + + fun textToImage(engineId: String, payload: TextToImagePayload): Single + + fun imageToImage( + engineId: String, + payload: ImageToImagePayload, + imageBytes: ByteArray, + ): Single + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionGenerationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionGenerationDataSource.kt new file mode 100755 index 000000000..c4318e78c --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionGenerationDataSource.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +sealed interface StableDiffusionGenerationDataSource { + interface Remote : StableDiffusionGenerationDataSource { + fun checkAvailability(): Completable + fun checkAvailability(url: String): Completable + fun textToImage(payload: TextToImagePayload): Single + fun imageToImage(payload: ImageToImagePayload): Single + fun interruptGeneration(): Completable + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionHyperNetworksDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionHyperNetworksDataSource.kt similarity index 81% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionHyperNetworksDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionHyperNetworksDataSource.kt index 55f217d06..0712b7932 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionHyperNetworksDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionHyperNetworksDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionModelsDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionModelsDataSource.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionModelsDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionModelsDataSource.kt index bd11b77ac..cb5cd90c6 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionModelsDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionModelsDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionSamplersDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionSamplersDataSource.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionSamplersDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionSamplersDataSource.kt index ad49c8267..e1026aceb 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/StableDiffusionSamplersDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/StableDiffusionSamplersDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SupportersDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SupportersDataSource.kt similarity index 78% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SupportersDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SupportersDataSource.kt index 754646061..41faa04a1 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SupportersDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SupportersDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.Supporter +import dev.minios.pdaiv1.domain.entity.Supporter import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiGenerationDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiGenerationDataSource.kt new file mode 100644 index 000000000..1db66951f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiGenerationDataSource.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.domain.datasource + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +sealed interface SwarmUiGenerationDataSource { + + interface Remote : SwarmUiGenerationDataSource { + fun textToImage( + sessionId: String, + model: String, + payload: TextToImagePayload, + ): Single + + fun imageToImage( + sessionId: String, + model: String, + payload: ImageToImagePayload, + ): Single + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiModelsDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiModelsDataSource.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiModelsDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiModelsDataSource.kt index f47f21c77..200eb596b 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiModelsDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiModelsDataSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel +import dev.minios.pdaiv1.domain.entity.SwarmUiModel import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiSessionDataSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiSessionDataSource.kt similarity index 85% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiSessionDataSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiSessionDataSource.kt index af6807db1..812a49085 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/datasource/SwarmUiSessionDataSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/datasource/SwarmUiSessionDataSource.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.datasource +package dev.minios.pdaiv1.domain.datasource import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/demo/ImageToImageDemo.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/demo/ImageToImageDemo.kt new file mode 100644 index 000000000..dc78da539 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/demo/ImageToImageDemo.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.demo + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import io.reactivex.rxjava3.core.Single + +fun interface ImageToImageDemo { + fun getDemoBase64(payload: ImageToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/demo/TextToImageDemo.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/demo/TextToImageDemo.kt new file mode 100644 index 000000000..e21735759 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/demo/TextToImageDemo.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.demo + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +fun interface TextToImageDemo { + fun getDemoBase64(payload: TextToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/di/DomainModule.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/di/DomainModule.kt new file mode 100755 index 000000000..69e2006f5 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/di/DomainModule.kt @@ -0,0 +1,250 @@ +package dev.minios.pdaiv1.domain.di + +import dev.minios.pdaiv1.domain.interactor.settings.SetupConnectionInterActor +import dev.minios.pdaiv1.domain.interactor.settings.SetupConnectionInterActorImpl +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActor +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActorImpl +import dev.minios.pdaiv1.domain.usecase.caching.ClearAppCacheUseCase +import dev.minios.pdaiv1.domain.usecase.caching.ClearAppCacheUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.caching.DataPreLoaderUseCase +import dev.minios.pdaiv1.domain.usecase.caching.DataPreLoaderUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.caching.GetLastResultFromCacheUseCase +import dev.minios.pdaiv1.domain.usecase.caching.GetLastResultFromCacheUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.caching.SaveLastResultToCacheUseCase +import dev.minios.pdaiv1.domain.usecase.caching.SaveLastResultToCacheUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.ObserveSeverConnectivityUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.ObserveSeverConnectivityUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.PingStableDiffusionServiceUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.PingStableDiffusionServiceUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.TestConnectivityUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestConnectivityUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHordeApiKeyUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHordeApiKeyUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.TestFalAiApiKeyUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestFalAiApiKeyUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.connectivity.TestSwarmUiConnectivityUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestSwarmUiConnectivityUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.debug.DebugInsertBadBase64UseCase +import dev.minios.pdaiv1.domain.usecase.debug.DebugInsertBadBase64UseCaseImpl +import dev.minios.pdaiv1.domain.usecase.donate.FetchAndGetSupportersUseCase +import dev.minios.pdaiv1.domain.usecase.donate.FetchAndGetSupportersUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.DeleteModelUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.DeleteModelUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.DownloadModelUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.DownloadModelUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalModelUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalModelUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalQnnModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalQnnModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.ObserveLocalOnnxModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.ObserveLocalOnnxModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.downloadable.ScanCustomModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.ScanCustomModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.forgemodule.GetForgeModulesUseCase +import dev.minios.pdaiv1.domain.usecase.forgemodule.GetForgeModulesUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllGalleryUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllGalleryUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.GetAllGalleryUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetAllGalleryUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsRawUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsRawUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryPagedIdsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryPagedIdsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.GetMediaStoreInfoUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetMediaStoreInfoUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.GetThumbnailInfoUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetThumbnailInfoUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleImageVisibilityUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleImageVisibilityUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleLikeUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleLikeUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllUnlikedUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllUnlikedUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.LikeItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.LikeItemsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.UnlikeItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.UnlikeItemsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.HideItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.HideItemsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.gallery.UnhideItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.UnhideItemsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.FalAiGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.FalAiGenerationUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.GetRandomImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetRandomImageUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.ImageToImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ImageToImageUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.SaveGenerationResultUseCase +import dev.minios.pdaiv1.domain.usecase.generation.SaveGenerationResultUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.generation.TextToImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.TextToImageUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase +import dev.minios.pdaiv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.report.SendReportUseCase +import dev.minios.pdaiv1.domain.usecase.report.SendReportUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCase +import dev.minios.pdaiv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCase +import dev.minios.pdaiv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.sdlora.FetchAndGetLorasUseCase +import dev.minios.pdaiv1.domain.usecase.sdlora.FetchAndGetLorasUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCase +import dev.minios.pdaiv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCase +import dev.minios.pdaiv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase +import dev.minios.pdaiv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToA1111UseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToA1111UseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHordeUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHordeUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHuggingFaceUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHuggingFaceUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToLocalDiffusionUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToLocalDiffusionUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToMediaPipeUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToMediaPipeUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToQnnUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToQnnUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToOpenAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToOpenAiUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToFalAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToFalAiUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToStabilityAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToStabilityAiUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToSwarmUiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToSwarmUiUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.GetConfigurationUseCase +import dev.minios.pdaiv1.domain.usecase.settings.GetConfigurationUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.settings.SetServerConfigurationUseCase +import dev.minios.pdaiv1.domain.usecase.settings.SetServerConfigurationUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.splash.SplashNavigationUseCase +import dev.minios.pdaiv1.domain.usecase.splash.SplashNavigationUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.stabilityai.FetchAndGetStabilityAiEnginesUseCase +import dev.minios.pdaiv1.domain.usecase.stabilityai.FetchAndGetStabilityAiEnginesUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.stabilityai.ObserveStabilityAiCreditsUseCase +import dev.minios.pdaiv1.domain.usecase.stabilityai.ObserveStabilityAiCreditsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.swarmmodel.FetchAndGetSwarmUiModelsUseCase +import dev.minios.pdaiv1.domain.usecase.swarmmodel.FetchAndGetSwarmUiModelsUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.wakelock.AcquireWakelockUseCase +import dev.minios.pdaiv1.domain.usecase.wakelock.AcquireWakelockUseCaseImpl +import dev.minios.pdaiv1.domain.usecase.wakelock.ReleaseWakeLockUseCase +import dev.minios.pdaiv1.domain.usecase.wakelock.ReleaseWakeLockUseCaseImpl +import org.koin.core.module.dsl.factoryOf +import org.koin.dsl.bind +import org.koin.dsl.module + +internal val useCasesModule = module { + factoryOf(::TextToImageUseCaseImpl) bind TextToImageUseCase::class + factoryOf(::ImageToImageUseCaseImpl) bind ImageToImageUseCase::class + factoryOf(::FalAiGenerationUseCaseImpl) bind FalAiGenerationUseCase::class + factoryOf(::PingStableDiffusionServiceUseCaseImpl) bind PingStableDiffusionServiceUseCase::class + factoryOf(::ClearAppCacheUseCaseImpl) bind ClearAppCacheUseCase::class + factoryOf(::DataPreLoaderUseCaseImpl) bind DataPreLoaderUseCase::class + factoryOf(::FetchAndGetSwarmUiModelsUseCaseImpl) bind FetchAndGetSwarmUiModelsUseCase::class + factoryOf(::GetStableDiffusionModelsUseCaseImpl) bind GetStableDiffusionModelsUseCase::class + factoryOf(::SelectStableDiffusionModelUseCaseImpl) bind SelectStableDiffusionModelUseCase::class + factoryOf(::GetGenerationResultPagedUseCaseImpl) bind GetGenerationResultPagedUseCase::class + factoryOf(::GetAllGalleryUseCaseImpl) bind GetAllGalleryUseCase::class + factoryOf(::GetGalleryItemsUseCaseImpl) bind GetGalleryItemsUseCase::class + factoryOf(::GetGalleryItemsRawUseCaseImpl) bind GetGalleryItemsRawUseCase::class + factoryOf(::GetGalleryPagedIdsUseCaseImpl) bind GetGalleryPagedIdsUseCase::class + factoryOf(::GetThumbnailInfoUseCaseImpl) bind GetThumbnailInfoUseCase::class + factoryOf(::GetGenerationResultUseCaseImpl) bind GetGenerationResultUseCase::class + factoryOf(::DeleteGalleryItemUseCaseImpl) bind DeleteGalleryItemUseCase::class + factoryOf(::DeleteGalleryItemsUseCaseImpl) bind DeleteGalleryItemsUseCase::class + factoryOf(::DeleteAllGalleryUseCaseImpl) bind DeleteAllGalleryUseCase::class + factoryOf(::GetStableDiffusionSamplersUseCaseImpl) bind GetStableDiffusionSamplersUseCase::class + factoryOf(::FetchAndGetLorasUseCaseImpl) bind FetchAndGetLorasUseCase::class + factoryOf(::FetchAndGetHyperNetworksUseCaseImpl) bind FetchAndGetHyperNetworksUseCase::class + factoryOf(::FetchAndGetEmbeddingsUseCaseImpl) bind FetchAndGetEmbeddingsUseCase::class + factoryOf(::GetForgeModulesUseCaseImpl) bind GetForgeModulesUseCase::class + factoryOf(::SplashNavigationUseCaseImpl) bind SplashNavigationUseCase::class + factoryOf(::GetConfigurationUseCaseImpl) bind GetConfigurationUseCase::class + factoryOf(::SetServerConfigurationUseCaseImpl) bind SetServerConfigurationUseCase::class + factoryOf(::TestConnectivityUseCaseImpl) bind TestConnectivityUseCase::class + factoryOf(::TestHordeApiKeyUseCaseImpl) bind TestHordeApiKeyUseCase::class + factoryOf(::TestHuggingFaceApiKeyUseCaseImpl) bind TestHuggingFaceApiKeyUseCase::class + factoryOf(::TestOpenAiApiKeyUseCaseImpl) bind TestOpenAiApiKeyUseCase::class + factoryOf(::TestStabilityAiApiKeyUseCaseImpl) bind TestStabilityAiApiKeyUseCase::class + factoryOf(::TestFalAiApiKeyUseCaseImpl) bind TestFalAiApiKeyUseCase::class + factoryOf(::TestSwarmUiConnectivityUseCaseImpl) bind TestSwarmUiConnectivityUseCase::class + factoryOf(::SaveGenerationResultUseCaseImpl) bind SaveGenerationResultUseCase::class + factoryOf(::ObserveSeverConnectivityUseCaseImpl) bind ObserveSeverConnectivityUseCase::class + factoryOf(::ObserveHordeProcessStatusUseCaseImpl) bind ObserveHordeProcessStatusUseCase::class + factoryOf(::GetMediaStoreInfoUseCaseImpl) bind GetMediaStoreInfoUseCase::class + factoryOf(::ToggleImageVisibilityUseCaseImpl) bind ToggleImageVisibilityUseCase::class + factoryOf(::ToggleLikeUseCaseImpl) bind ToggleLikeUseCase::class + factoryOf(::DeleteAllUnlikedUseCaseImpl) bind DeleteAllUnlikedUseCase::class + factoryOf(::LikeItemsUseCaseImpl) bind LikeItemsUseCase::class + factoryOf(::UnlikeItemsUseCaseImpl) bind UnlikeItemsUseCase::class + factoryOf(::HideItemsUseCaseImpl) bind HideItemsUseCase::class + factoryOf(::UnhideItemsUseCaseImpl) bind UnhideItemsUseCase::class + factoryOf(::GetRandomImageUseCaseImpl) bind GetRandomImageUseCase::class + factoryOf(::SaveLastResultToCacheUseCaseImpl) bind SaveLastResultToCacheUseCase::class + factoryOf(::GetLastResultFromCacheUseCaseImpl) bind GetLastResultFromCacheUseCase::class + factoryOf(::ObserveLocalDiffusionProcessStatusUseCaseImpl) bind ObserveLocalDiffusionProcessStatusUseCase::class + factoryOf(::GetLocalOnnxModelsUseCaseImpl) bind GetLocalOnnxModelsUseCase::class + factoryOf(::GetLocalMediaPipeModelsUseCaseImpl) bind GetLocalMediaPipeModelsUseCase::class + factoryOf(::GetLocalQnnModelsUseCaseImpl) bind GetLocalQnnModelsUseCase::class + factoryOf(::ScanCustomModelsUseCaseImpl) bind ScanCustomModelsUseCase::class + factoryOf(::DownloadModelUseCaseImpl) bind DownloadModelUseCase::class + factoryOf(::ObserveLocalOnnxModelsUseCaseImpl) bind ObserveLocalOnnxModelsUseCase::class + factoryOf(::DeleteModelUseCaseImpl) bind DeleteModelUseCase::class + factoryOf(::AcquireWakelockUseCaseImpl) bind AcquireWakelockUseCase::class + factoryOf(::ReleaseWakeLockUseCaseImpl) bind ReleaseWakeLockUseCase::class + factoryOf(::InterruptGenerationUseCaseImpl) bind InterruptGenerationUseCase::class + factoryOf(::ConnectToHordeUseCaseImpl) bind ConnectToHordeUseCase::class + factoryOf(::ConnectToLocalDiffusionUseCaseImpl) bind ConnectToLocalDiffusionUseCase::class + factoryOf(::ConnectToMediaPipeUseCaseImpl) bind ConnectToMediaPipeUseCase::class + factoryOf(::ConnectToQnnUseCaseImpl) bind ConnectToQnnUseCase::class + factoryOf(::ConnectToA1111UseCaseImpl) bind ConnectToA1111UseCase::class + factoryOf(::ConnectToSwarmUiUseCaseImpl) bind ConnectToSwarmUiUseCase::class + factoryOf(::ConnectToHuggingFaceUseCaseImpl) bind ConnectToHuggingFaceUseCase::class + factoryOf(::ConnectToOpenAiUseCaseImpl) bind ConnectToOpenAiUseCase::class + factoryOf(::ConnectToStabilityAiUseCaseImpl) bind ConnectToStabilityAiUseCase::class + factoryOf(::ConnectToFalAiUseCaseImpl) bind ConnectToFalAiUseCase::class + factoryOf(::FetchAndGetHuggingFaceModelsUseCaseImpl) bind FetchAndGetHuggingFaceModelsUseCase::class + factoryOf(::ObserveStabilityAiCreditsUseCaseImpl) bind ObserveStabilityAiCreditsUseCase::class + factoryOf(::FetchAndGetStabilityAiEnginesUseCaseImpl) bind FetchAndGetStabilityAiEnginesUseCase::class + factoryOf(::FetchAndGetSupportersUseCaseImpl) bind FetchAndGetSupportersUseCase::class + factoryOf(::SendReportUseCaseImpl) bind SendReportUseCase::class + factoryOf(::GetLocalModelUseCaseImpl) bind GetLocalModelUseCase::class +} + +internal val interActorsModule = module { + factoryOf(::WakeLockInterActorImpl) bind WakeLockInterActor::class + factoryOf(::SetupConnectionInterActorImpl) bind SetupConnectionInterActor::class +} + +internal val debugModule = module { + factoryOf(::DebugInsertBadBase64UseCaseImpl) bind DebugInsertBadBase64UseCase::class +} + +val domainModule = (useCasesModule + interActorsModule + debugModule).toTypedArray() diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ADetailerConfig.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ADetailerConfig.kt new file mode 100644 index 000000000..14b7e7c7d --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ADetailerConfig.kt @@ -0,0 +1,74 @@ +package dev.minios.pdaiv1.domain.entity + +import java.io.Serializable + +/** + * Configuration for the ADetailer extension in A1111/Forge. + * ADetailer automatically detects faces/hands and refines them with a second pass. + * + * @param enabled Whether ADetailer should be used for generation. + * @param model The detection model to use (e.g., "face_yolov8n.pt", "hand_yolov8n.pt"). + * @param prompt Optional prompt override for the detected regions. + * @param negativePrompt Optional negative prompt override for the detected regions. + * @param confidence Detection confidence threshold (0.0 to 1.0). + * @param maskBlur Blur amount for the detected mask. + * @param denoisingStrength Denoising strength for the second pass (0.0 to 1.0). + * @param inpaintOnlyMasked Whether to inpaint only the masked region. + * @param inpaintPadding Padding around detected regions in pixels. + */ +data class ADetailerConfig( + val enabled: Boolean = false, + val model: String = "face_yolov8s.pt", + val prompt: String = "", + val negativePrompt: String = "", + val confidence: Float = 0.3f, + val maskBlur: Int = 4, + val denoisingStrength: Float = 0.4f, + val inpaintOnlyMasked: Boolean = true, + val inpaintPadding: Int = 32, +) : Serializable { + + companion object { + val DISABLED = ADetailerConfig(enabled = false) + + val AVAILABLE_MODELS = listOf( + "face_yolov8n.pt", + "face_yolov8s.pt", + "hand_yolov8n.pt", + "person_yolov8n-seg.pt", + "person_yolov8s-seg.pt", + "yolov8x-worldv2.pt", + "mediapipe_face_full", + "mediapipe_face_short", + "mediapipe_face_mesh", + "mediapipe_face_mesh_eyes_only", + ) + } + + /** + * Converts this config to the alwayson_scripts format expected by A1111 API. + */ + fun toAlwaysOnScripts(): Map? { + if (!enabled) return null + + return mapOf( + "ADetailer" to mapOf( + "args" to listOf( + true, // enable + false, // skip_img2img + mapOf( + "ad_model" to model, + "ad_prompt" to prompt, + "ad_negative_prompt" to negativePrompt, + "ad_confidence" to confidence, + "ad_dilate_erode" to maskBlur, + "ad_denoising_strength" to denoisingStrength, + "ad_inpaint_only_masked" to inpaintOnlyMasked, + "ad_inpaint_only_masked_padding" to inpaintPadding, + "is_api" to true, + ) + ) + ) + ) + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/AiGenerationResult.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/AiGenerationResult.kt similarity index 75% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/AiGenerationResult.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/AiGenerationResult.kt index 1c8b6a247..a6bdeebfd 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/AiGenerationResult.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/AiGenerationResult.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import java.util.Date @@ -21,6 +21,12 @@ data class AiGenerationResult( val subSeedStrength: Float, val denoisingStrength: Float, val hidden: Boolean, + val liked: Boolean = false, + val mediaPath: String = "", + val inputMediaPath: String = "", + val mediaType: MediaType = MediaType.IMAGE, + val modelName: String = "", + val blurHash: String = "", ) { enum class Type(val key: String) { TEXT_TO_IMAGE("txt2img"), diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/BackgroundWorkResult.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/BackgroundWorkResult.kt similarity index 83% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/BackgroundWorkResult.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/BackgroundWorkResult.kt index 88f197d56..5d0986fc3 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/BackgroundWorkResult.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/BackgroundWorkResult.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity sealed interface BackgroundWorkResult { data object None : BackgroundWorkResult diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/BackgroundWorkStatus.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/BackgroundWorkStatus.kt new file mode 100644 index 000000000..cb2c3fb65 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/BackgroundWorkStatus.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.entity + +data class BackgroundWorkStatus( + val running: Boolean, + val statusTitle: String, + val statusSubTitle: String, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ColorToken.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ColorToken.kt similarity index 87% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ColorToken.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/ColorToken.kt index e06593488..9b46cce0a 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ColorToken.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ColorToken.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity enum class ColorToken { ROSEWATER, diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Configuration.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Configuration.kt similarity index 76% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Configuration.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/Configuration.kt index 40c5e10a3..f8ded8ce7 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Configuration.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Configuration.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials data class Configuration( val serverUrl: String = "", @@ -14,9 +14,12 @@ data class Configuration( val huggingFaceModel: String = "", val stabilityAiApiKey: String = "", val stabilityAiEngineId: String = "", + val falAiApiKey: String = "", val authCredentials: AuthorizationCredentials = AuthorizationCredentials.None, val localOnnxModelId: String = "", val localOnnxModelPath: String = "", val localMediaPipeModelId: String = "", val localMediaPipeModelPath: String = "", + val localQnnModelId: String = "", + val localQnnModelPath: String = "", ) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/DarkThemeToken.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/DarkThemeToken.kt similarity index 79% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/DarkThemeToken.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/DarkThemeToken.kt index 60fb4bc93..c1a541244 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/DarkThemeToken.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/DarkThemeToken.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity enum class DarkThemeToken { FRAPPE, MACCHIATO, MOCHA; diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/DownloadState.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/DownloadState.kt similarity index 86% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/DownloadState.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/DownloadState.kt index 0f7c668fc..f6f978aba 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/DownloadState.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/DownloadState.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import java.io.File diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Embedding.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Embedding.kt new file mode 100644 index 000000000..0e0d68f75 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Embedding.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.domain.entity + +data class Embedding( + val keyword: String, +) diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FalAiEndpoint.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FalAiEndpoint.kt new file mode 100644 index 000000000..3f9a8ca1a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FalAiEndpoint.kt @@ -0,0 +1,97 @@ +package dev.minios.pdaiv1.domain.entity + +/** + * Represents a fal.ai endpoint loaded from OpenAPI schema. + * Can be either built-in (shipped with app) or custom (imported by user). + */ +data class FalAiEndpoint( + val id: String, + val endpointId: String, + val title: String, + val description: String, + val category: FalAiEndpointCategory, + val group: String, // For hierarchical display, e.g. "FLUX 2", "FLUX LoRA" + val thumbnailUrl: String, + val playgroundUrl: String, + val documentationUrl: String, + val isCustom: Boolean, + val schema: FalAiEndpointSchema, +) + +enum class FalAiEndpointCategory(val key: String, val displayName: String) { + TEXT_TO_IMAGE("text-to-image", "Text to Image"), + IMAGE_TO_IMAGE("image-to-image", "Image to Image"), + INPAINTING("inpainting", "Inpainting"), + UPSCALING("upscaling", "Upscaling"), + OTHER("other", "Other"); + + companion object { + fun fromKey(key: String): FalAiEndpointCategory = + entries.find { it.key.equals(key, ignoreCase = true) } ?: OTHER + } +} + +/** + * Schema definition for a fal.ai endpoint, parsed from OpenAPI JSON. + */ +data class FalAiEndpointSchema( + val baseUrl: String, + val submissionPath: String, + val inputProperties: List, + val requiredProperties: List, + val propertyOrder: List, +) + +/** + * Represents an input property from the OpenAPI schema. + */ +data class FalAiInputProperty( + val name: String, + val title: String, + val description: String, + val type: FalAiPropertyType, + val default: Any? = null, + val minimum: Number? = null, + val maximum: Number? = null, + val enumValues: List? = null, + val isRequired: Boolean = false, + val isImageInput: Boolean = false, + val arrayItemProperties: List? = null, + val linkedMaskProperty: String? = null, // For inpainting: links image_url to its mask_url +) + +enum class FalAiPropertyType { + STRING, + INTEGER, + NUMBER, + BOOLEAN, + ARRAY, + OBJECT, + IMAGE_URL, + IMAGE_URL_ARRAY, + INPAINT, // Combined image + mask drawing + ENUM, + IMAGE_SIZE; + + companion object { + fun fromString( + type: String?, + hasEnum: Boolean = false, + isImageField: Boolean = false, + isImageArray: Boolean = false, + ): FalAiPropertyType { + if (isImageArray) return IMAGE_URL_ARRAY + if (isImageField) return IMAGE_URL + if (hasEnum) return ENUM + return when (type?.lowercase()) { + "string" -> STRING + "integer" -> INTEGER + "number" -> NUMBER + "boolean" -> BOOLEAN + "array" -> ARRAY + "object" -> OBJECT + else -> STRING + } + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FalAiPayload.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FalAiPayload.kt new file mode 100644 index 000000000..faec99f38 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FalAiPayload.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.domain.entity + +data class FalAiPayload( + val endpointId: String, + val parameters: Map, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/FeatureTag.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FeatureTag.kt similarity index 78% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/FeatureTag.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/FeatureTag.kt index d4c7db9b3..020571bdd 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/FeatureTag.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/FeatureTag.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity enum class FeatureTag { Txt2Img, diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ForgeModule.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ForgeModule.kt new file mode 100644 index 000000000..1849b49cb --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ForgeModule.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.entity + +import java.io.Serializable + +data class ForgeModule( + val name: String, + val path: String, +) : Serializable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Grid.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Grid.kt new file mode 100644 index 000000000..a9309da83 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Grid.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.domain.entity + +enum class Grid(val size: Int) { + Fixed1(1), + Fixed2(2), + Fixed3(3), + Fixed4(4), + Fixed5(5), + Fixed6(6); + + companion object { + fun fromSize(size: Int): Grid = entries.find { it.size == size } ?: Fixed3 + + fun zoomIn(current: Grid): Grid { + val index = entries.indexOf(current) + return if (index > 0) entries[index - 1] else current + } + + fun zoomOut(current: Grid): Grid { + val index = entries.indexOf(current) + return if (index < entries.lastIndex) entries[index + 1] else current + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HiresConfig.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HiresConfig.kt new file mode 100644 index 000000000..19697cdc7 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HiresConfig.kt @@ -0,0 +1,49 @@ +package dev.minios.pdaiv1.domain.entity + +import java.io.Serializable + +/** + * Configuration for Hires. Fix (High Resolution Fix) in A1111/Forge. + * Allows upscaling generated images with additional refinement pass. + * + * @param enabled Whether Hires. Fix is enabled. + * @param upscaler The upscaler to use (e.g., "Latent", "R-ESRGAN 4x+"). + * @param scale The upscale factor (default: 2.0). + * @param steps The number of hires fix steps (0 = use same as first pass). + * @param denoisingStrength The denoising strength for the second pass. + * @param hrCfg CFG scale for hires pass (null = use server default based on model). + * @param hrDistilledCfg Distilled CFG scale for hires pass (null = use server default, used for Flux). + */ +data class HiresConfig( + val enabled: Boolean = false, + val upscaler: String = "None", + val scale: Float = 2.0f, + val steps: Int = 0, + val denoisingStrength: Float = 0.4f, + val hrCfg: Float? = null, + val hrDistilledCfg: Float? = null, +) : Serializable { + + companion object { + val DISABLED = HiresConfig(enabled = false) + + val AVAILABLE_UPSCALERS = listOf( + "Latent", + "Latent (antialiased)", + "Latent (bicubic)", + "Latent (bicubic antialiased)", + "Latent (nearest)", + "Latent (nearest-exact)", + "None", + "Lanczos", + "Nearest", + "ESRGAN_4x", + "LDSR", + "R-ESRGAN 4x+", + "R-ESRGAN 4x+ Anime6B", + "ScuNET GAN", + "ScuNET PSNR", + "SwinIR 4x", + ) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HordeProcessStatus.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HordeProcessStatus.kt new file mode 100644 index 000000000..ab29ecbca --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HordeProcessStatus.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.domain.entity + +data class HordeProcessStatus( + val waitTimeSeconds: Int, + val queuePosition: Int?, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/HuggingFaceModel.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HuggingFaceModel.kt similarity index 90% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/HuggingFaceModel.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/HuggingFaceModel.kt index e71fc8287..37e6d7d01 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/HuggingFaceModel.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/HuggingFaceModel.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity data class HuggingFaceModel( val id: String, diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ImageToImagePayload.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ImageToImagePayload.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ImageToImagePayload.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/ImageToImagePayload.kt index 6d44b612a..3d75cd749 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ImageToImagePayload.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ImageToImagePayload.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import java.io.Serializable @@ -17,6 +17,7 @@ data class ImageToImagePayload( val subSeed: String, val subSeedStrength: Float, val sampler: String, + val scheduler: Scheduler = Scheduler.AUTOMATIC, val nsfw: Boolean, val batchCount: Int, val inPaintingMaskInvert: Int, @@ -26,4 +27,6 @@ data class ImageToImagePayload( val maskBlur: Int, val stabilityAiClipGuidance: StabilityAiClipGuidance?, val stabilityAiStylePreset: StabilityAiStylePreset?, + val aDetailer: ADetailerConfig = ADetailerConfig.DISABLED, + val modelName: String = "", ) : Serializable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LoRA.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LoRA.kt new file mode 100644 index 000000000..1aa64a22b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LoRA.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.entity + +data class LoRA( + val name: String, + val alias: String, + val path: String, +) diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LocalAiModel.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LocalAiModel.kt new file mode 100644 index 000000000..6bd965086 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LocalAiModel.kt @@ -0,0 +1,49 @@ +package dev.minios.pdaiv1.domain.entity + +data class LocalAiModel( + val id: String, + val type: Type, + val name: String, + val size: String, + val sources: List, + val downloaded: Boolean = false, + val selected: Boolean = false, + val runOnCpu: Boolean = false, + val chipsetSuffix: String? = null, // "8gen1", "8gen2", "min", or null for all chips +) { + enum class Type(val key: String) { + ONNX("onnx"), + MediaPipe("mediapipe"), + QNN("qnn"); + + companion object { + fun parse(value: String?) = entries.find { it.key == value } ?: ONNX + } + } + + companion object { + val CustomOnnx = LocalAiModel( + id = "CUSTOM", + type = Type.ONNX, + name = "Custom", + size = "NaN", + sources = emptyList(), + ) + + val CustomMediaPipe = LocalAiModel( + id = "CUSTOM_MP", + type = Type.MediaPipe, + name = "Custom", + size = "NaN", + sources = emptyList(), + ) + + val CustomQnn = LocalAiModel( + id = "CUSTOM_QNN", + type = Type.QNN, + name = "Custom", + size = "NaN", + sources = emptyList(), + ) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LocalDiffusionStatus.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LocalDiffusionStatus.kt new file mode 100644 index 000000000..bdc2bfa29 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/LocalDiffusionStatus.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.entity + +import android.graphics.Bitmap + +data class LocalDiffusionStatus( + val current: Int, + val total: Int, + val previewBitmap: Bitmap? = null, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/MediaStoreInfo.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/MediaStoreInfo.kt similarity index 84% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/MediaStoreInfo.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/MediaStoreInfo.kt index 41694bc67..df1aae78a 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/MediaStoreInfo.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/MediaStoreInfo.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import android.net.Uri diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/MediaType.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/MediaType.kt new file mode 100644 index 000000000..45050152c --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/MediaType.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.domain.entity + +enum class MediaType(val key: String, val extension: String, val mimeType: String) { + IMAGE("IMAGE", "png", "image/png"), + VIDEO("VIDEO", "mp4", "video/mp4"); + + companion object { + fun parse(key: String): MediaType = entries.find { it.key == key } ?: IMAGE + + fun fromExtension(ext: String): MediaType = when (ext.lowercase()) { + "mp4", "webm", "mov" -> VIDEO + else -> IMAGE + } + + fun fromUrl(url: String): MediaType { + val ext = url.substringAfterLast('.').substringBefore('?').lowercase() + return fromExtension(ext) + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ModelType.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ModelType.kt new file mode 100644 index 000000000..b28b4dbed --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ModelType.kt @@ -0,0 +1,77 @@ +package dev.minios.pdaiv1.domain.entity + +/** + * Represents the type of Stable Diffusion model being used. + * Different model types have different generation parameters: + * - SD_1_5: Standard Stable Diffusion 1.x, uses CFG Scale and negative prompt + * - SDXL: Stable Diffusion XL, uses CFG Scale and negative prompt + * - FLUX: Flux models, uses Distilled CFG Scale, no negative prompt support + */ +enum class ModelType(val displayName: String) { + SD_1_5("SD 1.5"), + SDXL("SDXL"), + FLUX("Flux"); + + companion object { + /** + * Whether this model type supports negative prompts. + */ + fun ModelType.supportsNegativePrompt(): Boolean = this != FLUX + + /** + * Whether this model type uses distilled CFG scale instead of regular CFG. + */ + fun ModelType.usesDistilledCfg(): Boolean = this == FLUX + + /** + * Returns the default CFG Scale for this model type. + * Flux uses 1.0, SD/SDXL use 7.0. + */ + fun ModelType.defaultCfgScale(): Float = when (this) { + FLUX -> 1.0f + SD_1_5 -> 7.0f + SDXL -> 5.0f + } + + /** + * Returns the default Distilled CFG Scale for Flux models. + */ + fun ModelType.defaultDistilledCfgScale(): Float = 3.5f + + /** + * Returns the default sampler for this model type. + */ + fun ModelType.defaultSampler(): String = when (this) { + SD_1_5 -> "Euler a" + SDXL -> "DPM++ 2M SDE" + FLUX -> "Euler" + } + + /** + * Returns the default width for this model type. + */ + fun ModelType.defaultWidth(): Int = when (this) { + SD_1_5 -> 448 + SDXL -> 896 + FLUX -> 896 + } + + /** + * Returns the default height for this model type. + */ + fun ModelType.defaultHeight(): Int = when (this) { + SD_1_5 -> 576 + SDXL -> 1152 + FLUX -> 1152 + } + + /** + * Returns the default scheduler for this model type. + */ + fun ModelType.defaultScheduler(): Scheduler = when (this) { + SD_1_5 -> Scheduler.AUTOMATIC + SDXL -> Scheduler.KARRAS + FLUX -> Scheduler.SIMPLE + } + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiModel.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiModel.kt similarity index 85% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiModel.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiModel.kt index 8bbce6e1c..dacb3d4a4 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiModel.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiModel.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import java.io.Serializable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiQuality.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiQuality.kt new file mode 100644 index 000000000..4325784f4 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiQuality.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.domain.entity + +enum class OpenAiQuality(val key: String) { + STANDARD("standard"), + HD("hd"); +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiSize.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiSize.kt similarity index 94% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiSize.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiSize.kt index 454351b17..0c8b80a82 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/OpenAiSize.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiSize.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity enum class OpenAiSize( val key: String, diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiStyle.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiStyle.kt new file mode 100644 index 000000000..8630e72c9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/OpenAiStyle.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.domain.entity + +enum class OpenAiStyle(val key: String) { + VIVID("vivid"), + NATURAL("natural"); +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/QnnHiresConfig.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/QnnHiresConfig.kt new file mode 100644 index 000000000..d5704fec3 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/QnnHiresConfig.kt @@ -0,0 +1,43 @@ +package dev.minios.pdaiv1.domain.entity + +import java.io.Serializable + +/** + * Configuration for Hires.Fix in QNN NPU mode. + * + * Hires.Fix for QNN works by: + * 1. Generating image at base resolution (512x512) + * 2. Upscaling to target resolution (768 or 1024 variants) + * 3. Running img2img pass at target resolution for detail refinement + * + * Only available for NPU mode. CPU/GPU models are trained on 512 max. + * + * @param enabled Whether Hires.Fix is enabled. + * @param targetWidth Target width after upscaling (768 or 1024). + * @param targetHeight Target height after upscaling (768 or 1024). + * @param steps Number of refinement steps (0 = use same as first pass). + * @param denoisingStrength Denoising strength for the refinement pass (0.0-1.0). + */ +data class QnnHiresConfig( + val enabled: Boolean = false, + val targetWidth: Int = 1024, + val targetHeight: Int = 1024, + val steps: Int = 0, + val denoisingStrength: Float = 0.4f, +) : Serializable { + + companion object { + val DISABLED = QnnHiresConfig(enabled = false) + + /** + * Default config with 1024x1024 target. + */ + val DEFAULT = QnnHiresConfig( + enabled = false, + targetWidth = 1024, + targetHeight = 1024, + steps = 0, + denoisingStrength = 0.4f, + ) + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ReportReason.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ReportReason.kt similarity index 77% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ReportReason.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/ReportReason.kt index bda8a4569..ad97d62c1 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ReportReason.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ReportReason.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity enum class ReportReason { IntellectualPropertyInfringement, diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Scheduler.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Scheduler.kt new file mode 100644 index 000000000..6dd092922 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Scheduler.kt @@ -0,0 +1,39 @@ +package dev.minios.pdaiv1.domain.entity + +/** + * Scheduler types supported by Stable Diffusion and Flux models. + * Flux models typically use specific schedulers like "simple" or "normal". + * + * @param alias The API value sent to the server. + * @param displayName Human-readable name for UI. + */ +enum class Scheduler( + val alias: String, + val displayName: String, +) { + AUTOMATIC("automatic", "Automatic"), + UNIFORM("uniform", "Uniform"), + KARRAS("karras", "Karras"), + EXPONENTIAL("exponential", "Exponential"), + POLYEXPONENTIAL("polyexponential", "Polyexponential"), + SGM_UNIFORM("sgm_uniform", "SGM Uniform"), + KL_OPTIMAL("kl_optimal", "KL Optimal"), + ALIGN_YOUR_STEPS("align_your_steps", "Align Your Steps"), + SIMPLE("simple", "Simple"), + NORMAL("normal", "Normal"), + DDIM("ddim_uniform", "DDIM Uniform"), + BETA("beta", "Beta"); + + companion object { + /** + * Schedulers recommended for Flux models. + */ + val FLUX_RECOMMENDED = listOf(SIMPLE, NORMAL, BETA) + + /** + * Get scheduler by alias, defaulting to AUTOMATIC if not found. + */ + fun fromAlias(alias: String): Scheduler = + entries.find { it.alias.equals(alias, ignoreCase = true) } ?: AUTOMATIC + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ServerConfiguration.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ServerConfiguration.kt new file mode 100755 index 000000000..a41bd21b2 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ServerConfiguration.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.domain.entity + +data class ServerConfiguration( + val sdModelCheckpoint: String, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ServerSource.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ServerSource.kt similarity index 79% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ServerSource.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/ServerSource.kt index 15244e1da..1f379aa5d 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/ServerSource.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ServerSource.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity -import com.shifthackz.aisdv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.appbuild.BuildType enum class ServerSource( val key: String, @@ -65,6 +65,14 @@ enum class ServerSource( FeatureTag.Batch, ), ), + FAL_AI( + key = "fal_ai", + featureTags = setOf( + FeatureTag.Txt2Img, + FeatureTag.MultipleModels, + FeatureTag.Batch, + ), + ), LOCAL_MICROSOFT_ONNX( key = "local", featureTags = setOf( @@ -81,6 +89,16 @@ enum class ServerSource( FeatureTag.MultipleModels, ), allowedInBuilds = setOf(BuildType.PLAY, BuildType.FULL), + ), + LOCAL_QUALCOMM_QNN( + key = "local_qualcomm_qnn", + featureTags = setOf( + FeatureTag.Offline, + FeatureTag.Txt2Img, + FeatureTag.Img2Img, + FeatureTag.MultipleModels, + ), + allowedInBuilds = setOf(BuildType.PLAY, BuildType.FULL), ); companion object { diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Settings.kt similarity index 86% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/Settings.kt index c4da9e467..f9826984d 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Settings.kt @@ -1,10 +1,11 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken data class Settings( val serverUrl: String = "", val sdModel: String = "", + val modelType: ModelType = ModelType.SD_1_5, val demoMode: Boolean = false, val developerMode: Boolean = false, val localDiffusionAllowCancel: Boolean = false, diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiClipGuidance.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiClipGuidance.kt similarity index 79% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiClipGuidance.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiClipGuidance.kt index d67d1f81f..246a594f3 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiClipGuidance.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiClipGuidance.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import java.io.Serializable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiEngine.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiEngine.kt new file mode 100644 index 000000000..5889a71d9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiEngine.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.domain.entity + +data class StabilityAiEngine( + val id: String, + val name: String, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiSampler.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiSampler.kt similarity index 82% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiSampler.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiSampler.kt index bb086f9b2..dca153649 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiSampler.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiSampler.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity enum class StabilityAiSampler { NONE, diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiStylePreset.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiStylePreset.kt similarity index 93% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiStylePreset.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiStylePreset.kt index edc89d220..26e84be6a 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StabilityAiStylePreset.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StabilityAiStylePreset.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import java.io.Serializable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionHyperNetwork.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionHyperNetwork.kt new file mode 100644 index 000000000..819e900c9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionHyperNetwork.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.domain.entity + +data class StableDiffusionHyperNetwork( + val name: String, + val path: String, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionModel.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionModel.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionModel.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionModel.kt index 855b60a89..498288ccb 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/StableDiffusionModel.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionModel.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity data class StableDiffusionModel( val title: String, diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionSampler.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionSampler.kt new file mode 100755 index 000000000..53a6e1fdd --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/StableDiffusionSampler.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.entity + +data class StableDiffusionSampler( + val name: String, + val aliases: List, + val options: Map, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Supporter.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Supporter.kt similarity index 75% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Supporter.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/entity/Supporter.kt index eaffbeaf3..90c7e4bbf 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Supporter.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/Supporter.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.entity +package dev.minios.pdaiv1.domain.entity import java.util.Date diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/SwarmUiModel.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/SwarmUiModel.kt new file mode 100644 index 000000000..d136c9e20 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/SwarmUiModel.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.entity + +class SwarmUiModel( + val name: String, + val title: String, + val author: String, +) diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/TextToImagePayload.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/TextToImagePayload.kt new file mode 100755 index 000000000..7254b3b01 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/TextToImagePayload.kt @@ -0,0 +1,31 @@ +package dev.minios.pdaiv1.domain.entity + +import java.io.Serializable + +data class TextToImagePayload( + val prompt: String, + val negativePrompt: String, + val samplingSteps: Int, + val cfgScale: Float, + val distilledCfgScale: Float = 3.5f, + val width: Int, + val height: Int, + val restoreFaces: Boolean, + val seed: String, + val subSeed: String, + val subSeedStrength: Float, + val sampler: String, + val scheduler: Scheduler = Scheduler.AUTOMATIC, + val nsfw: Boolean, + val batchCount: Int, + val style: String?, + val quality: String?, + val openAiModel: OpenAiModel?, + val stabilityAiClipGuidance: StabilityAiClipGuidance?, + val stabilityAiStylePreset: StabilityAiStylePreset?, + val aDetailer: ADetailerConfig = ADetailerConfig.DISABLED, + val hires: HiresConfig = HiresConfig.DISABLED, + val qnnHires: QnnHiresConfig = QnnHiresConfig.DISABLED, + val forgeModules: List = emptyList(), + val modelName: String = "", +) : Serializable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ThumbnailData.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ThumbnailData.kt new file mode 100644 index 000000000..e2d55fdaa --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/entity/ThumbnailData.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.entity + +/** + * Lightweight data class for thumbnail loading. + * Contains only necessary fields without heavy Base64 image data. + */ +data class ThumbnailData( + val id: Long, + val mediaPath: String, + val hidden: Boolean, + val blurHash: String, +) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/extensions/RxRetry.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/extensions/RxRetry.kt similarity index 96% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/extensions/RxRetry.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/extensions/RxRetry.kt index 3e64d7030..a9ca4e9fa 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/extensions/RxRetry.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/extensions/RxRetry.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.extensions +package dev.minios.pdaiv1.domain.extensions import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Function diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/feature/MediaFileManager.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/MediaFileManager.kt new file mode 100644 index 000000000..ece0b3722 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/MediaFileManager.kt @@ -0,0 +1,92 @@ +package dev.minios.pdaiv1.domain.feature + +import dev.minios.pdaiv1.domain.entity.MediaType +import java.io.File + +/** + * Manages media file storage for generated images and videos. + * All media is stored in context.filesDir/media/ directory. + */ +interface MediaFileManager { + + /** + * Saves media data to a file and returns the relative path. + * @param data The raw bytes of the media file + * @param type The type of media (IMAGE or VIDEO) + * @return Relative path to the saved file (e.g., "media/abc123.png") + */ + fun saveMedia(data: ByteArray, type: MediaType): String + + /** + * Saves media from a URL by downloading it. + * @param url The URL to download from + * @param type The type of media + * @return Relative path to the saved file + */ + fun saveMediaFromUrl(url: String, type: MediaType): String + + /** + * Saves input media (for img2img, inpainting) to the input directory. + * @param data The raw bytes of the media file + * @param type The type of media + * @return Relative path to the saved file (e.g., "input/xyz789.png") + */ + fun saveInputMedia(data: ByteArray, type: MediaType): String + + /** + * Loads media file contents. + * @param path Relative path to the file + * @return File contents as bytes, or null if not found + */ + fun loadMedia(path: String): ByteArray? + + /** + * Deletes a media file. + * @param path Relative path to the file + * @return true if deleted successfully + */ + fun deleteMedia(path: String): Boolean + + /** + * Gets the absolute File reference for a media path. + * @param path Relative path to the file + * @return File object + */ + fun getMediaFile(path: String): File + + /** + * Migrates base64-encoded data to a file. + * Used for database migration from base64 storage to file storage. + * @param base64 Base64-encoded data + * @param type The type of media + * @return Relative path to the saved file + */ + fun migrateBase64ToFile(base64: String, type: MediaType): String + + /** + * Checks if the path represents file storage (vs legacy base64 or URL). + * @param path The path or data string to check + * @return true if this is a file path + */ + fun isFilePath(path: String): Boolean + + /** + * Checks if the path represents a video URL (legacy format). + * @param path The path or data string to check + * @return true if this is a VIDEO_URL: prefixed string + */ + fun isVideoUrl(path: String): Boolean + + /** + * Extracts video URL from legacy VIDEO_URL: format. + * @param path The VIDEO_URL: prefixed string + * @return The actual URL, or null if not a video URL + */ + fun extractVideoUrl(path: String): String? + + companion object { + const val MEDIA_DIR = "media" + const val INPUT_DIR = "input" + const val VIDEO_URL_PREFIX = "VIDEO_URL:" + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/auth/AuthorizationCredentials.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/auth/AuthorizationCredentials.kt similarity index 91% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/feature/auth/AuthorizationCredentials.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/feature/auth/AuthorizationCredentials.kt index 7821099c2..512bd600f 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/auth/AuthorizationCredentials.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/auth/AuthorizationCredentials.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.feature.auth +package dev.minios.pdaiv1.domain.feature.auth sealed interface AuthorizationCredentials { diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/auth/AuthorizationStore.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/auth/AuthorizationStore.kt similarity index 77% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/feature/auth/AuthorizationStore.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/feature/auth/AuthorizationStore.kt index 11d954790..f0dc520ca 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/auth/AuthorizationStore.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/auth/AuthorizationStore.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.feature.auth +package dev.minios.pdaiv1.domain.feature.auth interface AuthorizationStore { fun getAuthorizationCredentials(): AuthorizationCredentials diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/feature/diffusion/LocalDiffusion.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/diffusion/LocalDiffusion.kt new file mode 100644 index 000000000..3f9898131 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/diffusion/LocalDiffusion.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.domain.feature.diffusion + +import android.graphics.Bitmap +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +interface LocalDiffusion { + fun process(payload: TextToImagePayload): Single + fun interrupt(): Completable + fun observeStatus(): Observable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/feature/mediapipe/MediaPipe.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/mediapipe/MediaPipe.kt new file mode 100644 index 000000000..8da67a0f8 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/mediapipe/MediaPipe.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.feature.mediapipe + +import android.graphics.Bitmap +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +interface MediaPipe { + fun process(payload: TextToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/feature/qnn/LocalQnn.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/qnn/LocalQnn.kt new file mode 100644 index 000000000..7a69ecc6d --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/qnn/LocalQnn.kt @@ -0,0 +1,62 @@ +package dev.minios.pdaiv1.domain.feature.qnn + +import android.graphics.Bitmap +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +/** + * Result of QNN generation containing the image and metadata. + */ +data class QnnGenerationResult( + val bitmap: Bitmap, + val seed: Long, + val width: Int, + val height: Int, +) + +/** + * Feature interface for Qualcomm QNN based Stable Diffusion inference. + * + * This runs a local HTTP server (localhost:8081) that handles generation requests. + * Supports both text-to-image and image-to-image generation. + */ +interface LocalQnn { + /** + * Process text-to-image generation request. + */ + fun processTextToImage(payload: TextToImagePayload): Single + + /** + * Process image-to-image generation request. + */ + fun processImageToImage(payload: ImageToImagePayload): Single + + /** + * Interrupt current generation. + */ + fun interrupt(): Completable + + /** + * Observe generation progress status. + */ + fun observeStatus(): Observable + + /** + * Check if QNN backend is available and running. + */ + fun isAvailable(): Single + + /** + * Start the QNN backend service. + */ + fun startService(): Completable + + /** + * Stop the QNN backend service. + */ + fun stopService(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/feature/work/BackgroundTaskManager.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/work/BackgroundTaskManager.kt new file mode 100644 index 000000000..c4cee1235 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/work/BackgroundTaskManager.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.domain.feature.work + +import dev.minios.pdaiv1.domain.entity.FalAiPayload +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload + +interface BackgroundTaskManager { + fun scheduleTextToImageTask(payload: TextToImagePayload) + fun scheduleImageToImageTask(payload: ImageToImagePayload) + fun scheduleFalAiTask(payload: FalAiPayload) + fun retryLastTextToImageTask(): Result + fun retryLastImageToImageTask(): Result + fun retryLastFalAiTask(): Result + fun cancelAll(): Result +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/feature/work/BackgroundWorkObserver.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/work/BackgroundWorkObserver.kt new file mode 100644 index 000000000..89e66cbfc --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/feature/work/BackgroundWorkObserver.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.domain.feature.work + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.BackgroundWorkResult +import dev.minios.pdaiv1.domain.entity.BackgroundWorkStatus +import io.reactivex.rxjava3.core.Flowable + +interface BackgroundWorkObserver { + fun observeStatus(): Flowable + fun observeResult(): Flowable + fun observeNewImage(): Flowable + fun observeGalleryChanged(): Flowable + fun dismissResult() + fun refreshStatus() + fun postStatusMessage(title: String, subTitle: String) + fun postSuccessSignal(result: List) + fun postNewImageSignal() + fun postGalleryChangedSignal() + fun postCancelSignal() + fun postFailedSignal(t: Throwable) + fun hasActiveTasks(): Boolean +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/DatabaseClearGateway.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/DatabaseClearGateway.kt similarity index 78% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/DatabaseClearGateway.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/gateway/DatabaseClearGateway.kt index 3b9ee198b..906e22b06 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/gateway/DatabaseClearGateway.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/DatabaseClearGateway.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.gateway +package dev.minios.pdaiv1.domain.gateway import io.reactivex.rxjava3.core.Completable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/MediaStoreGateway.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/MediaStoreGateway.kt new file mode 100644 index 000000000..9794e623d --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/MediaStoreGateway.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.gateway + +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import java.io.File + +interface MediaStoreGateway { + fun exportToFile(fileName: String, content: ByteArray) + fun exportFromFile(fileName: String, sourceFile: File) + fun getInfo(): MediaStoreInfo +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/ServerConnectivityGateway.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/ServerConnectivityGateway.kt new file mode 100644 index 000000000..82118d9ea --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/gateway/ServerConnectivityGateway.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.gateway + +import io.reactivex.rxjava3.core.Flowable + +fun interface ServerConnectivityGateway { + fun observe(): Flowable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/settings/SetupConnectionInterActor.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/settings/SetupConnectionInterActor.kt new file mode 100644 index 000000000..87cfd7237 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/settings/SetupConnectionInterActor.kt @@ -0,0 +1,25 @@ +package dev.minios.pdaiv1.domain.interactor.settings + +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToA1111UseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToFalAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHordeUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHuggingFaceUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToLocalDiffusionUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToMediaPipeUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToQnnUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToOpenAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToStabilityAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToSwarmUiUseCase + +interface SetupConnectionInterActor { + val connectToHorde: ConnectToHordeUseCase + val connectToLocal: ConnectToLocalDiffusionUseCase + val connectToMediaPipe: ConnectToMediaPipeUseCase + val connectToQnn: ConnectToQnnUseCase + val connectToA1111: ConnectToA1111UseCase + val connectToHuggingFace: ConnectToHuggingFaceUseCase + val connectToOpenAi: ConnectToOpenAiUseCase + val connectToStabilityAi: ConnectToStabilityAiUseCase + val connectToSwarmUi: ConnectToSwarmUiUseCase + val connectToFalAi: ConnectToFalAiUseCase +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/settings/SetupConnectionInterActorImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/settings/SetupConnectionInterActorImpl.kt new file mode 100644 index 000000000..68d3ac675 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/settings/SetupConnectionInterActorImpl.kt @@ -0,0 +1,25 @@ +package dev.minios.pdaiv1.domain.interactor.settings + +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToA1111UseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToFalAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHordeUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToHuggingFaceUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToLocalDiffusionUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToMediaPipeUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToQnnUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToOpenAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToStabilityAiUseCase +import dev.minios.pdaiv1.domain.usecase.settings.ConnectToSwarmUiUseCase + +internal data class SetupConnectionInterActorImpl( + override val connectToHorde: ConnectToHordeUseCase, + override val connectToLocal: ConnectToLocalDiffusionUseCase, + override val connectToMediaPipe: ConnectToMediaPipeUseCase, + override val connectToQnn: ConnectToQnnUseCase, + override val connectToA1111: ConnectToA1111UseCase, + override val connectToHuggingFace: ConnectToHuggingFaceUseCase, + override val connectToOpenAi: ConnectToOpenAiUseCase, + override val connectToStabilityAi: ConnectToStabilityAiUseCase, + override val connectToSwarmUi: ConnectToSwarmUiUseCase, + override val connectToFalAi: ConnectToFalAiUseCase, +) : SetupConnectionInterActor diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/wakelock/WakeLockInterActor.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/wakelock/WakeLockInterActor.kt new file mode 100644 index 000000000..160b7b0a3 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/wakelock/WakeLockInterActor.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.interactor.wakelock + +import dev.minios.pdaiv1.domain.usecase.wakelock.AcquireWakelockUseCase +import dev.minios.pdaiv1.domain.usecase.wakelock.ReleaseWakeLockUseCase + +interface WakeLockInterActor { + val acquireWakelockUseCase: AcquireWakelockUseCase + val releaseWakeLockUseCase: ReleaseWakeLockUseCase +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/wakelock/WakeLockInterActorImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/wakelock/WakeLockInterActorImpl.kt new file mode 100644 index 000000000..9f974f003 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/interactor/wakelock/WakeLockInterActorImpl.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.interactor.wakelock + +import dev.minios.pdaiv1.domain.usecase.wakelock.AcquireWakelockUseCase +import dev.minios.pdaiv1.domain.usecase.wakelock.ReleaseWakeLockUseCase + +internal data class WakeLockInterActorImpl( + override val acquireWakelockUseCase: AcquireWakelockUseCase, + override val releaseWakeLockUseCase: ReleaseWakeLockUseCase +) : WakeLockInterActor diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/preference/PreferenceManager.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/preference/PreferenceManager.kt new file mode 100644 index 000000000..63622d01b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/preference/PreferenceManager.kt @@ -0,0 +1,68 @@ +package dev.minios.pdaiv1.domain.preference + +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.Settings +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable + +interface PreferenceManager { + var automatic1111ServerUrl: String + var swarmUiServerUrl: String + var swarmUiModel: String + var demoMode: Boolean + var developerMode: Boolean + var localMediaPipeCustomModelPath: String + var localQnnCustomModelPath: String + var localOnnxCustomModelPath: String + var localOnnxAllowCancel: Boolean + var localOnnxSchedulerThread: SchedulersToken + var monitorConnectivity: Boolean + var autoSaveAiResults: Boolean + var saveToMediaStore: Boolean + var formAdvancedOptionsAlwaysShow: Boolean + var formPromptTaggedInput: Boolean + var source: ServerSource + var sdModel: String + var modelType: ModelType + var hordeApiKey: String + var openAiApiKey: String + var huggingFaceApiKey: String + var huggingFaceModel: String + var stabilityAiApiKey: String + var stabilityAiEngineId: String + var falAiApiKey: String + var falAiSelectedEndpointId: String + var onBoardingComplete: Boolean + var forceSetupAfterUpdate: Boolean + var localOnnxModelId: String + var localOnnxUseNNAPI: Boolean + var localOnnxLastPrompt: String + var localOnnxLastNegativePrompt: String + var localMediaPipeModelId: String + var localMediaPipeLastPrompt: String + var localMediaPipeLastNegativePrompt: String + var localQnnModelId: String + var localQnnRunOnCpu: Boolean + var localQnnUseOpenCL: Boolean + var localQnnScheduler: String + var localQnnShowDiffusionProcess: Boolean + var localQnnLastPrompt: String + var localQnnLastNegativePrompt: String + var designUseSystemColorPalette: Boolean + var designUseSystemDarkTheme: Boolean + var designDarkTheme: Boolean + var designColorToken: String + var designDarkThemeToken: String + var backgroundGeneration: Boolean + var backgroundProcessCount: Int + var galleryGrid: Grid + + fun observe(): Flowable + fun refresh(): Completable + + fun saveFalAiEndpointParams(endpointId: String, params: Map) + fun getFalAiEndpointParams(endpointId: String): Map +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/preference/SessionPreference.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/preference/SessionPreference.kt new file mode 100644 index 000000000..9dba2743b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/preference/SessionPreference.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.domain.preference + +interface SessionPreference { + var swarmUiSessionId: String +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/DownloadableModelRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/DownloadableModelRepository.kt new file mode 100644 index 000000000..a3f04394b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/DownloadableModelRepository.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +interface DownloadableModelRepository { + fun download(id: String, url: String): Observable + fun delete(id: String): Completable + fun getAllOnnx(): Single> + fun getAllMediaPipe(): Single> + fun getAllQnn(): Single> + fun observeAllOnnx(): Flowable> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/EmbeddingsRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/EmbeddingsRepository.kt new file mode 100644 index 000000000..6181701b9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/EmbeddingsRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.Embedding +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface EmbeddingsRepository { + fun fetchEmbeddings(): Completable + fun fetchAndGetEmbeddings(): Single> + fun getEmbeddings(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/FalAiEndpointRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/FalAiEndpointRepository.kt new file mode 100644 index 000000000..d2394aa4d --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/FalAiEndpointRepository.kt @@ -0,0 +1,49 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +interface FalAiEndpointRepository { + + /** + * Get all available endpoints (built-in + custom). + */ + fun observeAll(): Observable> + + /** + * Get all endpoints once. + */ + fun getAll(): Single> + + /** + * Get endpoint by ID. + */ + fun getById(id: String): Single + + /** + * Get currently selected endpoint. + */ + fun getSelected(): Single + + /** + * Set selected endpoint ID. + */ + fun setSelected(id: String): Completable + + /** + * Import custom endpoint from OpenAPI JSON string. + */ + fun importFromJson(json: String): Single + + /** + * Import custom endpoint from URL. + */ + fun importFromUrl(url: String): Single + + /** + * Delete custom endpoint. + */ + fun delete(id: String): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/FalAiGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/FalAiGenerationRepository.kt new file mode 100644 index 000000000..a336751e8 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/FalAiGenerationRepository.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +interface FalAiGenerationRepository { + fun validateApiKey(): Single + fun generateFromText(payload: TextToImagePayload): Single + fun generateDynamic(endpoint: FalAiEndpoint, parameters: Map): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ForgeModulesRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ForgeModulesRepository.kt new file mode 100644 index 000000000..5b893b8ea --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ForgeModulesRepository.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.ForgeModule +import io.reactivex.rxjava3.core.Single + +interface ForgeModulesRepository { + fun fetchModules(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/GenerationResultRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/GenerationResultRepository.kt new file mode 100644 index 000000000..d185a5f7e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/GenerationResultRepository.kt @@ -0,0 +1,60 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.domain.entity.ThumbnailData +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface GenerationResultRepository { + + fun getAll(): Single> + + fun getAllIds(): Single> + + fun getAllIdsWithBlurHash(): Single>> + + fun getThumbnailInfoByIds(idList: List): Single> + + fun getPage(limit: Int, offset: Int): Single> + + fun getMediaStoreInfo(): Single + + fun getById(id: Long): Single + + fun getByIds(idList: List): Single> + + /** + * Returns raw data without loading full images from files. + * Use this for thumbnail loading where only mediaPath is needed. + */ + fun getByIdsRaw(idList: List): Single> + + fun insert(result: AiGenerationResult): Single + + fun deleteById(id: Long): Completable + + fun deleteByIdList(idList: List): Completable + + fun deleteAll(): Completable + + fun deleteAllUnliked(): Completable + + fun toggleVisibility(id: Long): Single + + fun toggleLike(id: Long): Single + + fun likeByIds(idList: List): Completable + + fun unlikeByIds(idList: List): Completable + + fun hideByIds(idList: List): Completable + + fun unhideByIds(idList: List): Completable + + /** + * Migrates existing gallery items from base64 storage to file-based storage. + * This runs in the background at app startup. + */ + fun migrateBase64ToFiles(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HordeGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HordeGenerationRepository.kt new file mode 100644 index 000000000..5e56994eb --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HordeGenerationRepository.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single + +interface HordeGenerationRepository { + fun observeStatus(): Flowable + fun validateApiKey(): Single + fun generateFromText(payload: TextToImagePayload): Single + fun generateFromImage(payload: ImageToImagePayload): Single + fun interruptGeneration(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HuggingFaceGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HuggingFaceGenerationRepository.kt new file mode 100644 index 000000000..4c855d84d --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HuggingFaceGenerationRepository.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +interface HuggingFaceGenerationRepository { + fun validateApiKey(): Single + fun generateFromText(payload: TextToImagePayload): Single + fun generateFromImage(payload: ImageToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HuggingFaceModelsRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HuggingFaceModelsRepository.kt new file mode 100644 index 000000000..7700748a6 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/HuggingFaceModelsRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface HuggingFaceModelsRepository { + fun fetchHuggingFaceModels(): Completable + fun fetchAndGetHuggingFaceModels(): Single> + fun getHuggingFaceModels(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/LocalDiffusionGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/LocalDiffusionGenerationRepository.kt new file mode 100644 index 000000000..eb4ac85df --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/LocalDiffusionGenerationRepository.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +interface LocalDiffusionGenerationRepository { + fun observeStatus(): Observable + fun generateFromText(payload: TextToImagePayload): Single + fun interruptGeneration(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/LorasRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/LorasRepository.kt new file mode 100644 index 000000000..a34998885 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/LorasRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.LoRA +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface LorasRepository { + fun fetchLoras(): Completable + fun fetchAndGetLoras(): Single> + fun getLoras(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/MediaPipeGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/MediaPipeGenerationRepository.kt new file mode 100644 index 000000000..da33e1cb3 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/MediaPipeGenerationRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +interface MediaPipeGenerationRepository { + fun generateFromText(payload: TextToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/OpenAiGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/OpenAiGenerationRepository.kt new file mode 100644 index 000000000..db8299bf5 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/OpenAiGenerationRepository.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +interface OpenAiGenerationRepository { + fun validateApiKey(): Single + fun generateFromText(payload: TextToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/QnnGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/QnnGenerationRepository.kt new file mode 100644 index 000000000..2190965c1 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/QnnGenerationRepository.kt @@ -0,0 +1,19 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +/** + * Repository for Qualcomm QNN (NPU) based local image generation. + */ +interface QnnGenerationRepository { + fun observeStatus(): Observable + fun generateFromText(payload: TextToImagePayload): Single + fun generateFromImage(payload: ImageToImagePayload): Single + fun interruptGeneration(): Completable +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/RandomImageRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/RandomImageRepository.kt similarity index 75% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/repository/RandomImageRepository.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/repository/RandomImageRepository.kt index e975d378b..55fbbeac7 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/RandomImageRepository.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/RandomImageRepository.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.repository +package dev.minios.pdaiv1.domain.repository import android.graphics.Bitmap import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ReportRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ReportRepository.kt new file mode 100644 index 000000000..8163e3e6e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ReportRepository.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.ReportReason +import io.reactivex.rxjava3.core.Completable + +interface ReportRepository { + fun send(text: String, reason: ReportReason, image: String): Completable +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/ServerConfigurationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ServerConfigurationRepository.kt similarity index 76% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/repository/ServerConfigurationRepository.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/repository/ServerConfigurationRepository.kt index de59111d5..4d014ba5a 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/ServerConfigurationRepository.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/ServerConfigurationRepository.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.repository +package dev.minios.pdaiv1.domain.repository -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.domain.entity.ServerConfiguration import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiCreditsRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiCreditsRepository.kt similarity index 87% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiCreditsRepository.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiCreditsRepository.kt index 5235532bb..5d0881450 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/StabilityAiCreditsRepository.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiCreditsRepository.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.repository +package dev.minios.pdaiv1.domain.repository import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiEnginesRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiEnginesRepository.kt new file mode 100644 index 000000000..dd637edbd --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiEnginesRepository.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine +import io.reactivex.rxjava3.core.Single + +interface StabilityAiEnginesRepository { + fun fetchAndGet(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiGenerationRepository.kt new file mode 100644 index 000000000..bcd0c11a5 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StabilityAiGenerationRepository.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +interface StabilityAiGenerationRepository { + fun validateApiKey(): Single + fun generateFromText(payload: TextToImagePayload): Single + fun generateFromImage(payload: ImageToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionGenerationRepository.kt new file mode 100755 index 000000000..5c5ced09b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionGenerationRepository.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface StableDiffusionGenerationRepository { + fun checkApiAvailability(): Completable + fun checkApiAvailability(url: String): Completable + fun generateFromText(payload: TextToImagePayload): Single + fun generateFromImage(payload: ImageToImagePayload): Single + fun interruptGeneration(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionHyperNetworksRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionHyperNetworksRepository.kt new file mode 100644 index 000000000..0daa822a9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionHyperNetworksRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface StableDiffusionHyperNetworksRepository { + fun fetchHyperNetworks(): Completable + fun fetchAndGetHyperNetworks(): Single> + fun getHyperNetworks(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionModelsRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionModelsRepository.kt new file mode 100755 index 000000000..25767153e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionModelsRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface StableDiffusionModelsRepository { + fun fetchModels(): Completable + fun fetchAndGetModels(): Single> + fun getModels(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionSamplersRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionSamplersRepository.kt new file mode 100755 index 000000000..d76df3fdb --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/StableDiffusionSamplersRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface StableDiffusionSamplersRepository { + fun fetchSamplers(): Completable + + fun getSamplers(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SupportersRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SupportersRepository.kt new file mode 100644 index 000000000..d6d4776e3 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SupportersRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.Supporter +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface SupportersRepository { + fun fetchSupporters(): Completable + fun fetchAndGetSupporters(): Single> + fun getSupporters(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SwarmUiGenerationRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SwarmUiGenerationRepository.kt new file mode 100644 index 000000000..70209e918 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SwarmUiGenerationRepository.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface SwarmUiGenerationRepository { + fun checkApiAvailability(): Completable + fun checkApiAvailability(url: String): Completable + fun generateFromText(payload: TextToImagePayload): Single + fun generateFromImage(payload: ImageToImagePayload): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SwarmUiModelsRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SwarmUiModelsRepository.kt new file mode 100644 index 000000000..c637bfbf0 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/SwarmUiModelsRepository.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.repository + +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +interface SwarmUiModelsRepository { + fun fetchModels(): Completable + fun fetchAndGetModels(): Single> + fun getModels(): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/TemporaryGenerationResultRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/TemporaryGenerationResultRepository.kt similarity index 87% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/repository/TemporaryGenerationResultRepository.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/repository/TemporaryGenerationResultRepository.kt index 0d9ae6d58..83c73f1ad 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/TemporaryGenerationResultRepository.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/TemporaryGenerationResultRepository.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.repository +package dev.minios.pdaiv1.domain.repository -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.AiGenerationResult import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/repository/WakeLockRepository.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/WakeLockRepository.kt new file mode 100644 index 000000000..b48a68e45 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/repository/WakeLockRepository.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.repository + +import android.os.PowerManager + +interface WakeLockRepository { + val wakeLock: PowerManager.WakeLock +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCase.kt new file mode 100644 index 000000000..970434a20 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import io.reactivex.rxjava3.core.Completable + +interface ClearAppCacheUseCase { + operator fun invoke(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCaseImpl.kt new file mode 100644 index 000000000..085d7ec34 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCaseImpl.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.FileLoggingTree +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Completable + +internal class ClearAppCacheUseCaseImpl( + private val fileProviderDescriptor: FileProviderDescriptor, + private val repository: GenerationResultRepository, +) : ClearAppCacheUseCase { + + override fun invoke() = Completable.concatArray( + repository.deleteAll(), + Completable.fromAction { FileLoggingTree.clearLog(fileProviderDescriptor) }, + ) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCase.kt new file mode 100755 index 000000000..6d64f97fd --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import io.reactivex.rxjava3.core.Completable + +interface DataPreLoaderUseCase { + operator fun invoke(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCaseImpl.kt new file mode 100755 index 000000000..feb32ee64 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCaseImpl.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.entity.FeatureTag +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.EmbeddingsRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.LorasRepository +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionHyperNetworksRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionModelsRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionSamplersRepository +import io.reactivex.rxjava3.core.Completable + +internal class DataPreLoaderUseCaseImpl( + private val preferenceManager: PreferenceManager, + private val serverConfigurationRepository: ServerConfigurationRepository, + private val sdModelsRepository: StableDiffusionModelsRepository, + private val sdSamplersRepository: StableDiffusionSamplersRepository, + private val sdLorasRepository: LorasRepository, + private val sdHyperNetworksRepository: StableDiffusionHyperNetworksRepository, + private val sdEmbeddingsRepository: EmbeddingsRepository, + private val generationResultRepository: GenerationResultRepository, +) : DataPreLoaderUseCase { + + override operator fun invoke(): Completable { + // Always run migration first (will be no-op if already migrated) + val migrationCompletable = generationResultRepository + .migrateBase64ToFiles() + .doOnSubscribe { debugLog("Starting base64 to files migration...") } + .doOnComplete { debugLog("Base64 to files migration completed.") } + .onErrorComplete { t -> + errorLog(t, "Base64 to files migration failed") + true // Continue even if migration fails + } + + val source = preferenceManager.source + val requiresServerData = source.featureTags.contains(FeatureTag.OwnServer) + + // Skip server data fetching for sources that don't need it (Horde, HuggingFace, OpenAI, StabilityAI, FalAI, local) + if (!requiresServerData) { + return migrationCompletable + } + + // Only fetch server configuration and related data for A1111/SwarmUI + return migrationCompletable + .andThen(serverConfigurationRepository.fetchConfiguration()) + .andThen(sdModelsRepository.fetchModels()) + .andThen(sdSamplersRepository.fetchSamplers()) + .andThen(sdLorasRepository.fetchLoras()) + .andThen(sdHyperNetworksRepository.fetchHyperNetworks()) + .andThen(sdEmbeddingsRepository.fetchEmbeddings()) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCase.kt new file mode 100644 index 000000000..576af1ea5 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Single + +interface GetLastResultFromCacheUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImpl.kt new file mode 100644 index 000000000..25131d632 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import dev.minios.pdaiv1.domain.repository.TemporaryGenerationResultRepository + +internal class GetLastResultFromCacheUseCaseImpl( + private val temporaryGenerationResultRepository: TemporaryGenerationResultRepository, +) : GetLastResultFromCacheUseCase { + + override fun invoke() = temporaryGenerationResultRepository.get() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCase.kt new file mode 100644 index 000000000..e368f378a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Single + +interface SaveLastResultToCacheUseCase { + operator fun invoke(result: AiGenerationResult): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImpl.kt new file mode 100644 index 000000000..1562ea1a9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImpl.kt @@ -0,0 +1,19 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.TemporaryGenerationResultRepository +import io.reactivex.rxjava3.core.Single + +internal class SaveLastResultToCacheUseCaseImpl( + private val temporaryGenerationResultRepository: TemporaryGenerationResultRepository, + private val preferenceManager: PreferenceManager, +) : SaveLastResultToCacheUseCase { + + override fun invoke(result: AiGenerationResult): Single { + if (preferenceManager.autoSaveAiResults) return Single.just(result) + return temporaryGenerationResultRepository + .put(result) + .andThen(Single.just(result)) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCase.kt new file mode 100644 index 000000000..a7022bb65 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Flowable + +interface ObserveSeverConnectivityUseCase { + operator fun invoke(): Flowable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImpl.kt new file mode 100644 index 000000000..79e8bba65 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImpl.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.gateway.ServerConnectivityGateway + +internal class ObserveSeverConnectivityUseCaseImpl( + private val serverConnectivityGateway: ServerConnectivityGateway, +) : ObserveSeverConnectivityUseCase { + + override fun invoke() = serverConnectivityGateway + .observe() + .distinctUntilChanged() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCase.kt new file mode 100755 index 000000000..0da06e46a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Completable + +interface PingStableDiffusionServiceUseCase { + operator fun invoke(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImpl.kt new file mode 100755 index 000000000..5479e81e2 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImpl.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import io.reactivex.rxjava3.core.Completable + +internal class PingStableDiffusionServiceUseCaseImpl( + private val repository: StableDiffusionGenerationRepository, +) : PingStableDiffusionServiceUseCase { + + override operator fun invoke(): Completable = repository.checkApiAvailability() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCase.kt new file mode 100644 index 000000000..2a15661ca --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Completable + +interface TestConnectivityUseCase { + operator fun invoke(url: String): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCaseImpl.kt new file mode 100644 index 000000000..dcfacd2ac --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository + +internal class TestConnectivityUseCaseImpl( + private val repository: StableDiffusionGenerationRepository, +) : TestConnectivityUseCase { + + override fun invoke(url: String) = repository.checkApiAvailability(url) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCase.kt new file mode 100644 index 000000000..67a737f2f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Single + +interface TestFalAiApiKeyUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCaseImpl.kt new file mode 100644 index 000000000..446e06d77 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository + +internal class TestFalAiApiKeyUseCaseImpl( + private val falAiGenerationRepository: FalAiGenerationRepository, +) : TestFalAiApiKeyUseCase { + + override fun invoke() = falAiGenerationRepository.validateApiKey() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCase.kt new file mode 100644 index 000000000..3b78ea25f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Single + +interface TestHordeApiKeyUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImpl.kt new file mode 100644 index 000000000..f21dece91 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository + +internal class TestHordeApiKeyUseCaseImpl( + private val hordeGenerationRepository: HordeGenerationRepository, +) : TestHordeApiKeyUseCase { + + override fun invoke() = hordeGenerationRepository.validateApiKey() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCase.kt new file mode 100644 index 000000000..737c2fa35 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Single + +interface TestHuggingFaceApiKeyUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImpl.kt new file mode 100644 index 000000000..d89e8a71e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository + +internal class TestHuggingFaceApiKeyUseCaseImpl( + private val huggingFaceGenerationRepository: HuggingFaceGenerationRepository, +) : TestHuggingFaceApiKeyUseCase { + + override fun invoke() = huggingFaceGenerationRepository.validateApiKey() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCase.kt new file mode 100644 index 000000000..6f978948f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Single + +interface TestOpenAiApiKeyUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImpl.kt new file mode 100644 index 000000000..be22cea31 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.OpenAiGenerationRepository + +internal class TestOpenAiApiKeyUseCaseImpl( + private val openAiGenerationRepository: OpenAiGenerationRepository, +) : TestOpenAiApiKeyUseCase { + + override fun invoke() = openAiGenerationRepository.validateApiKey() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCase.kt new file mode 100644 index 000000000..f862c3c0d --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Single + +interface TestStabilityAiApiKeyUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImpl.kt new file mode 100644 index 000000000..fa0cb8bce --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository + +internal class TestStabilityAiApiKeyUseCaseImpl( + private val stabilityAiGenerationRepository: StabilityAiGenerationRepository, +) : TestStabilityAiApiKeyUseCase { + + override fun invoke() = stabilityAiGenerationRepository.validateApiKey() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCase.kt new file mode 100644 index 000000000..4aa723abe --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import io.reactivex.rxjava3.core.Completable + +interface TestSwarmUiConnectivityUseCase { + operator fun invoke(url: String): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImpl.kt new file mode 100644 index 000000000..42b4d1c25 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository + +class TestSwarmUiConnectivityUseCaseImpl( + private val repository: SwarmUiGenerationRepository, +) : TestSwarmUiConnectivityUseCase { + + override fun invoke(url: String) = repository.checkApiAvailability(url) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCase.kt new file mode 100644 index 000000000..6186e6235 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.debug + +import io.reactivex.rxjava3.core.Completable + +interface DebugInsertBadBase64UseCase { + operator fun invoke(): Completable +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt similarity index 82% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt index 738fc23af..0a6cb9e91 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.usecase.debug +package dev.minios.pdaiv1.domain.usecase.debug -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Completable import java.util.Date @@ -33,6 +33,7 @@ class DebugInsertBadBase64UseCaseImpl( subSeedStrength = 0f, denoisingStrength = 0f, hidden = false, + modelName = "Debug", ) } } diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCase.kt new file mode 100644 index 000000000..5b03f7b9a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.donate + +import dev.minios.pdaiv1.domain.entity.Supporter +import io.reactivex.rxjava3.core.Single + +interface FetchAndGetSupportersUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImpl.kt new file mode 100644 index 000000000..794bcca03 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImpl.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.donate + +import dev.minios.pdaiv1.domain.entity.Supporter +import dev.minios.pdaiv1.domain.repository.SupportersRepository +import io.reactivex.rxjava3.core.Single + +class FetchAndGetSupportersUseCaseImpl( + private val supportersRepository: SupportersRepository, +) : FetchAndGetSupportersUseCase { + + override fun invoke(): Single> = supportersRepository.fetchAndGetSupporters() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCase.kt new file mode 100644 index 000000000..5233e6899 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import io.reactivex.rxjava3.core.Completable + +interface DeleteModelUseCase { + operator fun invoke(id: String): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCaseImpl.kt new file mode 100644 index 000000000..3104136be --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository + +internal class DeleteModelUseCaseImpl( + private val downloadableModelRepository: DownloadableModelRepository, +) : DeleteModelUseCase { + + override fun invoke(id: String) = downloadableModelRepository.delete(id) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCase.kt new file mode 100644 index 000000000..2faacf162 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.DownloadState +import io.reactivex.rxjava3.core.Observable + +interface DownloadModelUseCase { + operator fun invoke(id: String, url: String): Observable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt new file mode 100644 index 000000000..4164c09fa --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository + +internal class DownloadModelUseCaseImpl( + private val downloadableModelRepository: DownloadableModelRepository, +) : DownloadModelUseCase { + + override fun invoke(id: String, url: String) = downloadableModelRepository.download(id, url) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCase.kt new file mode 100644 index 000000000..72683f688 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Single + +interface GetLocalMediaPipeModelsUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImpl.kt new file mode 100644 index 000000000..7b134ed5b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository + +internal class GetLocalMediaPipeModelsUseCaseImpl( + private val downloadableModelRepository: DownloadableModelRepository, + ) : GetLocalMediaPipeModelsUseCase { + + override fun invoke() = downloadableModelRepository.getAllMediaPipe() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCase.kt new file mode 100644 index 000000000..d8305f49c --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Single + +interface GetLocalModelUseCase { + operator fun invoke(id: String): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt new file mode 100644 index 000000000..e82f6f61a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Single + +internal class GetLocalModelUseCaseImpl( + private val localDataSource: DownloadableModelDataSource.Local, +) : GetLocalModelUseCase { + + override fun invoke(id: String): Single = localDataSource.getById(id) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCase.kt new file mode 100644 index 000000000..861d3e840 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Single + +interface GetLocalOnnxModelsUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImpl.kt new file mode 100644 index 000000000..ca3ba25c2 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository + +internal class GetLocalOnnxModelsUseCaseImpl( + private val downloadableModelRepository: DownloadableModelRepository, +) : GetLocalOnnxModelsUseCase { + + override fun invoke() = downloadableModelRepository.getAllOnnx() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCase.kt new file mode 100644 index 000000000..f729987d4 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Single + +interface GetLocalQnnModelsUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCaseImpl.kt new file mode 100644 index 000000000..01b4cc013 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository + +internal class GetLocalQnnModelsUseCaseImpl( + private val downloadableModelRepository: DownloadableModelRepository, +) : GetLocalQnnModelsUseCase { + + override fun invoke() = downloadableModelRepository.getAllQnn() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCase.kt new file mode 100644 index 000000000..782541f86 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Flowable + +interface ObserveLocalOnnxModelsUseCase { + operator fun invoke(): Flowable> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImpl.kt new file mode 100644 index 000000000..f43c9854c --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImpl.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository + +internal class ObserveLocalOnnxModelsUseCaseImpl( + private val repository: DownloadableModelRepository, +) : ObserveLocalOnnxModelsUseCase { + + override fun invoke() = repository + .observeAllOnnx() + .distinctUntilChanged() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCase.kt new file mode 100644 index 000000000..6bd686769 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Single + +interface ScanCustomModelsUseCase { + operator fun invoke(type: LocalAiModel.Type): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCaseImpl.kt new file mode 100644 index 000000000..9d9f62d76 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCaseImpl.kt @@ -0,0 +1,120 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.reactivex.rxjava3.core.Single +import java.io.File + +class ScanCustomModelsUseCaseImpl( + private val preferenceManager: PreferenceManager, +) : ScanCustomModelsUseCase { + + override fun invoke(type: LocalAiModel.Type): Single> = Single.create { emitter -> + try { + val basePath = when (type) { + LocalAiModel.Type.ONNX -> preferenceManager.localOnnxCustomModelPath + LocalAiModel.Type.MediaPipe -> preferenceManager.localMediaPipeCustomModelPath + LocalAiModel.Type.QNN -> preferenceManager.localQnnCustomModelPath + } + + val baseDir = File(basePath) + if (!baseDir.exists() || !baseDir.isDirectory) { + emitter.onSuccess(emptyList()) + return@create + } + + val models = baseDir.listFiles() + ?.filter { it.isDirectory } + ?.filter { dir -> isValidModelDirectory(dir, type) } + ?.map { dir -> + LocalAiModel( + id = "${getCustomPrefix(type)}:${dir.name}", + type = type, + name = dir.name, + size = formatSize(calculateDirSize(dir)), + sources = emptyList(), + downloaded = true, + selected = false, + ) + } + ?: emptyList() + + emitter.onSuccess(models) + } catch (e: Exception) { + emitter.onError(e) + } + } + + private fun getCustomPrefix(type: LocalAiModel.Type): String = when (type) { + LocalAiModel.Type.ONNX -> "CUSTOM_ONNX" + LocalAiModel.Type.MediaPipe -> "CUSTOM_MP" + LocalAiModel.Type.QNN -> "CUSTOM_QNN" + } + + private fun isValidModelDirectory(dir: File, type: LocalAiModel.Type): Boolean { + val files = dir.listFiles()?.map { it.name } ?: return false + + return when (type) { + LocalAiModel.Type.ONNX -> { + // ONNX requires: text_encoder/model.ort, unet/model.ort, vae_decoder/model.ort + // and tokenizer/vocab.json, tokenizer/merges.txt + val hasTextEncoder = File(dir, "text_encoder/model.ort").exists() + val hasUnet = File(dir, "unet/model.ort").exists() + val hasVaeDecoder = File(dir, "vae_decoder/model.ort").exists() + val hasVocab = File(dir, "tokenizer/vocab.json").exists() + val hasMerges = File(dir, "tokenizer/merges.txt").exists() + + hasTextEncoder && hasUnet && hasVaeDecoder && hasVocab && hasMerges + } + LocalAiModel.Type.MediaPipe -> { + // MediaPipe requires bins folder with model files + // Structure: model_folder/bins/* (multiple .bin files) + val binsDir = File(dir, "bins") + if (binsDir.exists() && binsDir.isDirectory) { + val binsFiles = binsDir.listFiles()?.map { it.name } ?: emptyList() + // Should not contain QNN-specific files + val isNotQnn = !binsFiles.any { it.startsWith("clip.") || it.startsWith("unet.") } + binsFiles.isNotEmpty() && isNotQnn + } else { + false + } + } + LocalAiModel.Type.QNN -> { + // QNN requires: clip + unet + vae_decoder + tokenizer.json + // NPU mode: clip.bin, unet.bin, vae_decoder.bin + // CPU mode: clip.mnn (or clip_v2.mnn), unet.mnn, vae_decoder.mnn + val hasClipBin = files.contains("clip.bin") + val hasClipMnn = files.contains("clip.mnn") || files.contains("clip_v2.mnn") + val hasUnetBin = files.contains("unet.bin") + val hasUnetMnn = files.contains("unet.mnn") + val hasVaeBin = files.contains("vae_decoder.bin") + val hasVaeMnn = files.contains("vae_decoder.mnn") + val hasTokenizer = files.contains("tokenizer.json") + + val hasNpuModel = hasClipBin && hasUnetBin && hasVaeBin + val hasCpuModel = hasClipMnn && hasUnetMnn && hasVaeMnn + + hasTokenizer && (hasNpuModel || hasCpuModel) + } + } + } + + private fun calculateDirSize(dir: File): Long { + return dir.walkTopDown() + .filter { it.isFile } + .map { it.length() } + .sum() + } + + private fun formatSize(bytes: Long): String { + val kb = bytes / 1024.0 + val mb = kb / 1024.0 + val gb = mb / 1024.0 + + return when { + gb >= 1.0 -> String.format("%.2f GB", gb) + mb >= 1.0 -> String.format("%.2f MB", mb) + else -> String.format("%.2f KB", kb) + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCase.kt new file mode 100644 index 000000000..ccccf9d1a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.forgemodule + +import dev.minios.pdaiv1.domain.entity.ForgeModule +import io.reactivex.rxjava3.core.Single + +interface GetForgeModulesUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCaseImpl.kt new file mode 100644 index 000000000..fc971a709 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCaseImpl.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.forgemodule + +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.domain.repository.ForgeModulesRepository +import io.reactivex.rxjava3.core.Single + +internal class GetForgeModulesUseCaseImpl( + private val repository: ForgeModulesRepository, +) : GetForgeModulesUseCase { + + override operator fun invoke(): Single> = repository.fetchModules() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCase.kt new file mode 100644 index 000000000..99239e6ed --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface DeleteAllGalleryUseCase { + operator fun invoke(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImpl.kt new file mode 100644 index 000000000..82e680f28 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImpl.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Completable + +internal class DeleteAllGalleryUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : DeleteAllGalleryUseCase { + + override fun invoke(): Completable = generationResultRepository.deleteAll() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllUnlikedUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllUnlikedUseCase.kt new file mode 100644 index 000000000..2987057d2 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllUnlikedUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface DeleteAllUnlikedUseCase { + operator fun invoke(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllUnlikedUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllUnlikedUseCaseImpl.kt new file mode 100644 index 000000000..8640107cb --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllUnlikedUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class DeleteAllUnlikedUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : DeleteAllUnlikedUseCase { + + override fun invoke() = generationResultRepository.deleteAllUnliked() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCase.kt new file mode 100644 index 000000000..c814f1a18 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface DeleteGalleryItemUseCase { + operator fun invoke(id: Long): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImpl.kt new file mode 100644 index 000000000..8ccb1ed2e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImpl.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Completable + +internal class DeleteGalleryItemUseCaseImpl( + private val repository: GenerationResultRepository, +) : DeleteGalleryItemUseCase { + + override fun invoke(id: Long): Completable = repository.deleteById(id) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCase.kt new file mode 100644 index 000000000..bdb07bd4a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface DeleteGalleryItemsUseCase { + operator fun invoke(ids: List): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImpl.kt new file mode 100644 index 000000000..eed22ba30 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImpl.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Completable + +internal class DeleteGalleryItemsUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : DeleteGalleryItemsUseCase { + + override fun invoke(ids: List): Completable = generationResultRepository.deleteByIdList(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCase.kt new file mode 100644 index 000000000..0bd9cc2ca --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Single + +interface GetAllGalleryUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCaseImpl.kt new file mode 100644 index 000000000..812f342a3 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class GetAllGalleryUseCaseImpl( + private val repository: GenerationResultRepository, +) : GetAllGalleryUseCase { + + override operator fun invoke() = repository.getAll() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsRawUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsRawUseCase.kt new file mode 100644 index 000000000..c26b4b6f2 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsRawUseCase.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Single + +/** + * Returns gallery items WITHOUT loading full images from files. + * Use this for thumbnail loading where only mediaPath is needed. + */ +interface GetGalleryItemsRawUseCase { + operator fun invoke(ids: List): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsRawUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsRawUseCaseImpl.kt new file mode 100644 index 000000000..60e044114 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsRawUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class GetGalleryItemsRawUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : GetGalleryItemsRawUseCase { + + override fun invoke(ids: List) = generationResultRepository.getByIdsRaw(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCase.kt new file mode 100644 index 000000000..34376fa04 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Single + +interface GetGalleryItemsUseCase { + operator fun invoke(ids: List): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCaseImpl.kt new file mode 100644 index 000000000..14c47cbf7 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class GetGalleryItemsUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : GetGalleryItemsUseCase { + + override fun invoke(ids: List) = generationResultRepository.getByIds(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCase.kt new file mode 100644 index 000000000..f9f5d5e7c --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCase.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Single + +/** + * Returns all gallery item IDs with BlurHash sorted by creation date (newest first). + * This is a lightweight operation that only fetches IDs and BlurHash strings. + */ +interface GetGalleryPagedIdsUseCase { + operator fun invoke(): Single> + fun withBlurHash(): Single>> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCaseImpl.kt new file mode 100644 index 000000000..0609a7e91 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCaseImpl.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Single + +class GetGalleryPagedIdsUseCaseImpl( + private val repository: GenerationResultRepository, +) : GetGalleryPagedIdsUseCase { + + override fun invoke(): Single> = repository.getAllIds() + + override fun withBlurHash(): Single>> = repository.getAllIdsWithBlurHash() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCase.kt new file mode 100644 index 000000000..cf83da322 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import io.reactivex.rxjava3.core.Single + +interface GetMediaStoreInfoUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImpl.kt new file mode 100644 index 000000000..5a33ff547 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +class GetMediaStoreInfoUseCaseImpl( + private val repository: GenerationResultRepository, +) : GetMediaStoreInfoUseCase { + + override fun invoke() = repository.getMediaStoreInfo() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetThumbnailInfoUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetThumbnailInfoUseCase.kt new file mode 100644 index 000000000..17842e919 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetThumbnailInfoUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.entity.ThumbnailData +import io.reactivex.rxjava3.core.Single + +interface GetThumbnailInfoUseCase { + operator fun invoke(ids: List): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetThumbnailInfoUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetThumbnailInfoUseCaseImpl.kt new file mode 100644 index 000000000..4c6facc0e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/GetThumbnailInfoUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class GetThumbnailInfoUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : GetThumbnailInfoUseCase { + + override fun invoke(ids: List) = generationResultRepository.getThumbnailInfoByIds(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/HideItemsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/HideItemsUseCase.kt new file mode 100644 index 000000000..53f8e7ead --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/HideItemsUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface HideItemsUseCase { + operator fun invoke(ids: List): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/HideItemsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/HideItemsUseCaseImpl.kt new file mode 100644 index 000000000..2618c84ca --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/HideItemsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class HideItemsUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : HideItemsUseCase { + + override fun invoke(ids: List) = generationResultRepository.hideByIds(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/LikeItemsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/LikeItemsUseCase.kt new file mode 100644 index 000000000..2635b0bfb --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/LikeItemsUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface LikeItemsUseCase { + operator fun invoke(ids: List): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/LikeItemsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/LikeItemsUseCaseImpl.kt new file mode 100644 index 000000000..c021f94e8 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/LikeItemsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class LikeItemsUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : LikeItemsUseCase { + + override fun invoke(ids: List) = generationResultRepository.likeByIds(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt new file mode 100644 index 000000000..72610d832 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Single + +interface ToggleImageVisibilityUseCase { + operator fun invoke(id: Long): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt new file mode 100644 index 000000000..d81f74568 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class ToggleImageVisibilityUseCaseImpl( + private val repository: GenerationResultRepository, +) : ToggleImageVisibilityUseCase { + + override fun invoke(id: Long) = repository.toggleVisibility(id) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleLikeUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleLikeUseCase.kt new file mode 100644 index 000000000..70fc1efec --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleLikeUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Single + +interface ToggleLikeUseCase { + operator fun invoke(id: Long): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleLikeUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleLikeUseCaseImpl.kt new file mode 100644 index 000000000..bde449ad5 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleLikeUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class ToggleLikeUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : ToggleLikeUseCase { + + override fun invoke(id: Long) = generationResultRepository.toggleLike(id) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCase.kt new file mode 100644 index 000000000..9ed8f2467 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface UnhideItemsUseCase { + operator fun invoke(ids: List): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCaseImpl.kt new file mode 100644 index 000000000..b91d520fb --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class UnhideItemsUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : UnhideItemsUseCase { + + override fun invoke(ids: List) = generationResultRepository.unhideByIds(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCase.kt new file mode 100644 index 000000000..af5f76800 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Completable + +interface UnlikeItemsUseCase { + operator fun invoke(ids: List): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCaseImpl.kt new file mode 100644 index 000000000..069768a69 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class UnlikeItemsUseCaseImpl( + private val generationResultRepository: GenerationResultRepository, +) : UnlikeItemsUseCase { + + override fun invoke(ids: List) = generationResultRepository.unlikeByIds(ids) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCase.kt new file mode 100644 index 000000000..632d05c0a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCase.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.FalAiPayload +import io.reactivex.rxjava3.core.Single + +interface FalAiGenerationUseCase { + operator fun invoke(payload: FalAiPayload): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCaseImpl.kt new file mode 100644 index 000000000..aa181f139 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCaseImpl.kt @@ -0,0 +1,30 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.FalAiPayload +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +class FalAiGenerationUseCaseImpl( + private val falAiEndpointRepository: FalAiEndpointRepository, + private val falAiGenerationRepository: FalAiGenerationRepository, + private val saveGenerationResultUseCase: SaveGenerationResultUseCase, +) : FalAiGenerationUseCase { + + override fun invoke(payload: FalAiPayload): Single> { + return falAiEndpointRepository.getAll() + .flatMap { endpoints -> + val endpoint = endpoints.find { it.id == payload.endpointId || it.endpointId == payload.endpointId } + ?: return@flatMap Single.error(Throwable("Endpoint not found: ${payload.endpointId}")) + + falAiGenerationRepository.generateDynamic(endpoint, payload.parameters) + .flatMap { results -> + val saveCompletables = results.map { saveGenerationResultUseCase(it) } + Completable.merge(saveCompletables) + .andThen(Single.just(results)) + } + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCase.kt new file mode 100644 index 000000000..7b15fa87e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Single + +interface GetGenerationResultPagedUseCase { + operator fun invoke(limit: Int, offset: Int): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImpl.kt new file mode 100644 index 000000000..09d61c496 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class GetGenerationResultPagedUseCaseImpl( + private val repository: GenerationResultRepository, +) : GetGenerationResultPagedUseCase { + + override operator fun invoke(limit: Int, offset: Int) = repository.getPage(limit, offset) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCase.kt new file mode 100644 index 000000000..a271b0c9b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Single + +interface GetGenerationResultUseCase { + operator fun invoke(id: Long): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCaseImpl.kt new file mode 100644 index 000000000..fd1fe27d9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class GetGenerationResultUseCaseImpl( + private val repository: GenerationResultRepository, +) : GetGenerationResultUseCase { + + override operator fun invoke(id: Long) = repository.getById(id) +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCase.kt new file mode 100644 index 000000000..4d1f2e31c --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import android.graphics.Bitmap +import io.reactivex.rxjava3.core.Single + +interface GetRandomImageUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCaseImpl.kt new file mode 100644 index 000000000..e29837f3f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.repository.RandomImageRepository + +class GetRandomImageUseCaseImpl( + private val randomImageRepository: RandomImageRepository, +) : GetRandomImageUseCase { + + override fun invoke() = randomImageRepository.fetchAndGet() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCase.kt new file mode 100644 index 000000000..40da5c0bd --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCase.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import io.reactivex.rxjava3.core.Single + +interface ImageToImageUseCase { + operator fun invoke(payload: ImageToImagePayload): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCaseImpl.kt new file mode 100644 index 000000000..47f000a25 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCaseImpl.kt @@ -0,0 +1,39 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +internal class ImageToImageUseCaseImpl( + private val stableDiffusionGenerationRepository: StableDiffusionGenerationRepository, + private val swarmUiGenerationRepository: SwarmUiGenerationRepository, + private val hordeGenerationRepository: HordeGenerationRepository, + private val huggingFaceGenerationRepository: HuggingFaceGenerationRepository, + private val stabilityAiGenerationRepository: StabilityAiGenerationRepository, + private val qnnGenerationRepository: QnnGenerationRepository, + private val preferenceManager: PreferenceManager, +) : ImageToImageUseCase { + + override fun invoke(payload: ImageToImagePayload) = Observable + .range(1, payload.batchCount) + .concatMapSingle { generate(payload) } + .toList() + + private fun generate(payload: ImageToImagePayload) = when (preferenceManager.source) { + ServerSource.AUTOMATIC1111 -> stableDiffusionGenerationRepository.generateFromImage(payload) + ServerSource.SWARM_UI -> swarmUiGenerationRepository.generateFromImage(payload) + ServerSource.HORDE -> hordeGenerationRepository.generateFromImage(payload) + ServerSource.HUGGING_FACE -> huggingFaceGenerationRepository.generateFromImage(payload) + ServerSource.STABILITY_AI -> stabilityAiGenerationRepository.generateFromImage(payload) + ServerSource.LOCAL_QUALCOMM_QNN -> qnnGenerationRepository.generateFromImage(payload) + else -> Single.error(IllegalStateException("Img2Img not yet supported on ${preferenceManager.source}!")) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCase.kt new file mode 100644 index 000000000..feaf274f4 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import io.reactivex.rxjava3.core.Completable + +interface InterruptGenerationUseCase { + operator fun invoke(): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCaseImpl.kt new file mode 100644 index 000000000..4e916baac --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCaseImpl.kt @@ -0,0 +1,26 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import io.reactivex.rxjava3.core.Completable + +internal class InterruptGenerationUseCaseImpl( + private val stableDiffusionGenerationRepository: StableDiffusionGenerationRepository, + private val hordeGenerationRepository: HordeGenerationRepository, + private val localDiffusionGenerationRepository: LocalDiffusionGenerationRepository, + private val qnnGenerationRepository: QnnGenerationRepository, + private val preferenceManager: PreferenceManager, +) : InterruptGenerationUseCase { + + override fun invoke() = when (preferenceManager.source) { + ServerSource.AUTOMATIC1111 -> stableDiffusionGenerationRepository.interruptGeneration() + ServerSource.HORDE -> hordeGenerationRepository.interruptGeneration() + ServerSource.LOCAL_MICROSOFT_ONNX -> localDiffusionGenerationRepository.interruptGeneration() + ServerSource.LOCAL_QUALCOMM_QNN -> qnnGenerationRepository.interruptGeneration() + else -> Completable.complete() + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCase.kt new file mode 100644 index 000000000..188582232 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import io.reactivex.rxjava3.core.Flowable + +interface ObserveHordeProcessStatusUseCase { + operator fun invoke(): Flowable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImpl.kt new file mode 100644 index 000000000..322fd5a26 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImpl.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository + +internal class ObserveHordeProcessStatusUseCaseImpl( + private val hordeGenerationRepository: HordeGenerationRepository, +) : ObserveHordeProcessStatusUseCase { + + override fun invoke() = hordeGenerationRepository + .observeStatus() + .distinctUntilChanged() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCase.kt new file mode 100644 index 000000000..7459f6c52 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import io.reactivex.rxjava3.core.Observable + +interface ObserveLocalDiffusionProcessStatusUseCase { + operator fun invoke(): Observable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImpl.kt new file mode 100644 index 000000000..3be8906b4 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImpl.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import io.reactivex.rxjava3.core.Observable + +internal class ObserveLocalDiffusionProcessStatusUseCaseImpl( + private val localDiffusionGenerationRepository: LocalDiffusionGenerationRepository, + private val qnnGenerationRepository: QnnGenerationRepository, +) : ObserveLocalDiffusionProcessStatusUseCase { + + override fun invoke() = Observable.merge( + localDiffusionGenerationRepository.observeStatus(), + qnnGenerationRepository.observeStatus() + ).distinctUntilChanged() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCase.kt new file mode 100644 index 000000000..c006ae0c3 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.Completable + +interface SaveGenerationResultUseCase { + operator fun invoke(result: AiGenerationResult): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCaseImpl.kt new file mode 100644 index 000000000..5de896361 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCaseImpl.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository + +internal class SaveGenerationResultUseCaseImpl( + private val repository: GenerationResultRepository, +) : SaveGenerationResultUseCase { + + override fun invoke(result: AiGenerationResult) = repository + .insert(result) + .ignoreElement() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCase.kt new file mode 100755 index 000000000..aff06bf0f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCase.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import io.reactivex.rxjava3.core.Single + +interface TextToImageUseCase { + operator fun invoke(payload: TextToImagePayload): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCaseImpl.kt new file mode 100755 index 000000000..d0a46dc42 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCaseImpl.kt @@ -0,0 +1,53 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.MediaPipeGenerationRepository +import dev.minios.pdaiv1.domain.repository.OpenAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +internal class TextToImageUseCaseImpl( + private val stableDiffusionGenerationRepository: StableDiffusionGenerationRepository, + private val hordeGenerationRepository: HordeGenerationRepository, + private val huggingFaceGenerationRepository: HuggingFaceGenerationRepository, + private val openAiGenerationRepository: OpenAiGenerationRepository, + private val stabilityAiGenerationRepository: StabilityAiGenerationRepository, + private val falAiGenerationRepository: FalAiGenerationRepository, + private val swarmUiGenerationRepository: SwarmUiGenerationRepository, + private val localDiffusionGenerationRepository: LocalDiffusionGenerationRepository, + private val mediaPipeGenerationRepository: MediaPipeGenerationRepository, + private val qnnGenerationRepository: QnnGenerationRepository, + private val preferenceManager: PreferenceManager, +) : TextToImageUseCase { + + override operator fun invoke( + payload: TextToImagePayload, + ): Single> = Observable + .range(1, payload.batchCount) + .concatMapSingle { generate(payload) } + .toList() + + private fun generate(payload: TextToImagePayload) = when (preferenceManager.source) { + ServerSource.HORDE -> hordeGenerationRepository.generateFromText(payload) + ServerSource.LOCAL_MICROSOFT_ONNX -> localDiffusionGenerationRepository.generateFromText(payload) + ServerSource.HUGGING_FACE -> huggingFaceGenerationRepository.generateFromText(payload) + ServerSource.AUTOMATIC1111 -> stableDiffusionGenerationRepository.generateFromText(payload) + ServerSource.OPEN_AI -> openAiGenerationRepository.generateFromText(payload) + ServerSource.STABILITY_AI -> stabilityAiGenerationRepository.generateFromText(payload) + ServerSource.FAL_AI -> falAiGenerationRepository.generateFromText(payload) + ServerSource.SWARM_UI -> swarmUiGenerationRepository.generateFromText(payload) + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> mediaPipeGenerationRepository.generateFromText(payload) + ServerSource.LOCAL_QUALCOMM_QNN -> qnnGenerationRepository.generateFromText(payload) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCase.kt new file mode 100644 index 000000000..b3bdf5433 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.huggingface + +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import io.reactivex.rxjava3.core.Single + +interface FetchAndGetHuggingFaceModelsUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImpl.kt new file mode 100644 index 000000000..8358f9df2 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImpl.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.domain.usecase.huggingface + +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HuggingFaceModelsRepository +import io.reactivex.rxjava3.core.Single + +internal class FetchAndGetHuggingFaceModelsUseCaseImpl( + private val preferenceManager: PreferenceManager, + private val huggingFaceModelsRepository: HuggingFaceModelsRepository, +) : FetchAndGetHuggingFaceModelsUseCase { + + override fun invoke(): Single> { + if (preferenceManager.source != ServerSource.HUGGING_FACE) { + return Single.just(emptyList()) + } + return huggingFaceModelsRepository.fetchAndGetHuggingFaceModels() + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCase.kt new file mode 100644 index 000000000..48037fed4 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.report + +import dev.minios.pdaiv1.domain.entity.ReportReason +import io.reactivex.rxjava3.core.Completable + +interface SendReportUseCase { + operator fun invoke(text: String, reason: ReportReason, image: String): Completable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCaseImpl.kt new file mode 100644 index 000000000..2ed517a38 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCaseImpl.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.domain.usecase.report + +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.domain.repository.ReportRepository + +class SendReportUseCaseImpl( + private val repository: ReportRepository, +) : SendReportUseCase { + + override fun invoke(text: String, reason: ReportReason, image: String) = + repository.send(text, reason, image) + +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCase.kt new file mode 100644 index 000000000..7df96f6e9 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.sdembedding + +import dev.minios.pdaiv1.domain.entity.Embedding +import io.reactivex.rxjava3.core.Single + +interface FetchAndGetEmbeddingsUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImpl.kt new file mode 100644 index 000000000..74d4a1a67 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImpl.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.sdembedding + +import dev.minios.pdaiv1.domain.entity.Embedding +import dev.minios.pdaiv1.domain.repository.EmbeddingsRepository +import io.reactivex.rxjava3.core.Single + +internal class FetchAndGetEmbeddingsUseCaseImpl( + private val repository: EmbeddingsRepository, +) : FetchAndGetEmbeddingsUseCase { + + override fun invoke(): Single> = repository.fetchAndGetEmbeddings() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCase.kt new file mode 100644 index 000000000..a13e60800 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.sdhypernet + +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork +import io.reactivex.rxjava3.core.Single + +interface FetchAndGetHyperNetworksUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImpl.kt new file mode 100644 index 000000000..ff390bfb1 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImpl.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.usecase.sdhypernet + +import dev.minios.pdaiv1.domain.repository.StableDiffusionHyperNetworksRepository + +internal class FetchAndGetHyperNetworksUseCaseImpl( + private val stableDiffusionHyperNetworksRepository: StableDiffusionHyperNetworksRepository, +) : FetchAndGetHyperNetworksUseCase { + override fun invoke() = stableDiffusionHyperNetworksRepository.fetchAndGetHyperNetworks() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCase.kt new file mode 100644 index 000000000..5d5d8dcca --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.sdlora + +import dev.minios.pdaiv1.domain.entity.LoRA +import io.reactivex.rxjava3.core.Single + +interface FetchAndGetLorasUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImpl.kt new file mode 100644 index 000000000..8731d9682 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImpl.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.domain.usecase.sdlora + +import dev.minios.pdaiv1.domain.repository.LorasRepository + +internal class FetchAndGetLorasUseCaseImpl( + private val lorasRepository: LorasRepository, +) : FetchAndGetLorasUseCase { + + override fun invoke() = lorasRepository.fetchAndGetLoras() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCase.kt new file mode 100644 index 000000000..e07dd559f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.sdmodel + +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel +import io.reactivex.rxjava3.core.Single + +interface GetStableDiffusionModelsUseCase { + operator fun invoke(): Single>> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImpl.kt new file mode 100644 index 000000000..65e733e80 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImpl.kt @@ -0,0 +1,34 @@ +package dev.minios.pdaiv1.domain.usecase.sdmodel + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionModelsRepository +import io.reactivex.rxjava3.core.Single + +internal class GetStableDiffusionModelsUseCaseImpl( + private val preferenceManager: PreferenceManager, + private val serverConfigurationRepository: ServerConfigurationRepository, + private val sdModelsRepository: StableDiffusionModelsRepository, +) : GetStableDiffusionModelsUseCase { + + override operator fun invoke(): Single>> { + // Only fetch models if A1111/Forge is the active source + if (preferenceManager.source != ServerSource.AUTOMATIC1111) { + return Single.just(emptyList()) + } + return serverConfigurationRepository + .fetchAndGetConfiguration() + .flatMap { config -> + sdModelsRepository + .fetchAndGetModels() + .map { sdModels -> config to sdModels } + } + .map { (config, sdModels) -> + sdModels.map { model -> + model to (config.sdModelCheckpoint == model.title) + } + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCase.kt new file mode 100644 index 000000000..4f9ddcbe4 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.sdmodel + +import io.reactivex.rxjava3.core.Completable + +interface SelectStableDiffusionModelUseCase { + operator fun invoke(modelName: String): Completable +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImpl.kt similarity index 77% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImpl.kt index fa918a4b0..0ebff917f 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImpl.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.usecase.sdmodel +package dev.minios.pdaiv1.domain.usecase.sdmodel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository import io.reactivex.rxjava3.core.Completable internal class SelectStableDiffusionModelUseCaseImpl( diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCase.kt new file mode 100644 index 000000000..983fad19f --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.sdsampler + +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import io.reactivex.rxjava3.core.Single + +interface GetStableDiffusionSamplersUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImpl.kt new file mode 100644 index 000000000..38264d058 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImpl.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.domain.usecase.sdsampler + +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.domain.repository.StableDiffusionSamplersRepository +import io.reactivex.rxjava3.core.Single + +internal class GetStableDiffusionSamplersUseCaseImpl( + private val repository: StableDiffusionSamplersRepository, +) : GetStableDiffusionSamplersUseCase { + + override operator fun invoke(): Single> = repository + .getSamplers() +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCase.kt new file mode 100644 index 000000000..a4bc4ee81 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCase.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import io.reactivex.rxjava3.core.Single + +interface ConnectToA1111UseCase { + operator fun invoke( + url: String, + isDemo: Boolean, + credentials: AuthorizationCredentials, + ): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCaseImpl.kt similarity index 80% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCaseImpl.kt index 89e73ef59..4b86f01dc 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCaseImpl.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.core.common.reactive.retryWithDelay -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.usecase.caching.DataPreLoaderUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestConnectivityUseCase +import dev.minios.pdaiv1.core.common.reactive.retryWithDelay +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.usecase.caching.DataPreLoaderUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestConnectivityUseCase import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCase.kt new file mode 100644 index 000000000..09097a19e --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToFalAiUseCase { + operator fun invoke(apiKey: String, endpointId: String): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCaseImpl.kt new file mode 100644 index 000000000..8c260820a --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCaseImpl.kt @@ -0,0 +1,45 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.connectivity.TestFalAiApiKeyUseCase +import io.reactivex.rxjava3.core.Single +import java.util.concurrent.TimeUnit + +internal class ConnectToFalAiUseCaseImpl( + private val getConfigurationUseCase: GetConfigurationUseCase, + private val setServerConfigurationUseCase: SetServerConfigurationUseCase, + private val testFalAiApiKeyUseCase: TestFalAiApiKeyUseCase, + private val preferenceManager: PreferenceManager, +) : ConnectToFalAiUseCase { + + override fun invoke(apiKey: String, endpointId: String): Single> { + var configuration: Configuration? = null + return getConfigurationUseCase.invoke() + .map { originalConfiguration -> + configuration = originalConfiguration + originalConfiguration.copy( + source = ServerSource.FAL_AI, + falAiApiKey = apiKey, + authCredentials = AuthorizationCredentials.None, + ) + } + .flatMapCompletable(setServerConfigurationUseCase::invoke) + .doOnComplete { preferenceManager.falAiSelectedEndpointId = endpointId } + .delay(3L, TimeUnit.SECONDS) + .andThen(testFalAiApiKeyUseCase()) + .flatMap { + if (it) Single.just(Result.success(Unit)) + else Single.error(IllegalStateException("Bad key")) + } + .onErrorResumeNext { t -> + val rollback = configuration + ?.copy(authCredentials = AuthorizationCredentials.None) + ?: return@onErrorResumeNext Single.just(Result.failure(t)) + setServerConfigurationUseCase(rollback) + .andThen(Single.just(Result.failure(t))) + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCase.kt new file mode 100644 index 000000000..3008a2b3b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToHordeUseCase { + operator fun invoke(apiKey: String): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCaseImpl.kt similarity index 82% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCaseImpl.kt index 07458212f..15bf379da 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCaseImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHordeApiKeyUseCase +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHordeApiKeyUseCase import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import java.util.concurrent.TimeUnit diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCase.kt new file mode 100644 index 000000000..f1d9479fa --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToHuggingFaceUseCase { + operator fun invoke(apiKey: String, model: String): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImpl.kt similarity index 82% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImpl.kt index 9a4cb8736..3beb3d1d5 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCase +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCase import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import java.util.concurrent.TimeUnit diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCase.kt new file mode 100644 index 000000000..7e0650997 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToLocalDiffusionUseCase { + operator fun invoke(modelId: String): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImpl.kt similarity index 87% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImpl.kt index d2519425b..73a634cfb 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImpl.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.ServerSource import io.reactivex.rxjava3.core.Single internal class ConnectToLocalDiffusionUseCaseImpl( diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCase.kt new file mode 100644 index 000000000..ac0349ec7 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToMediaPipeUseCase { + operator fun invoke(modelId: String): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImpl.kt similarity index 87% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImpl.kt index a60bdc68f..916c5f0c2 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImpl.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.ServerSource import io.reactivex.rxjava3.core.Single internal class ConnectToMediaPipeUseCaseImpl( diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCase.kt new file mode 100644 index 000000000..4b5211824 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToOpenAiUseCase { + operator fun invoke(apiKey: String): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCaseImpl.kt similarity index 82% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCaseImpl.kt index 85e8881ad..fab26cce2 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCaseImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCase +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCase import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import java.util.concurrent.TimeUnit diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCase.kt new file mode 100644 index 000000000..66afe8978 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToQnnUseCase { + operator fun invoke(modelId: String, runOnCpu: Boolean = false): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCaseImpl.kt new file mode 100644 index 000000000..b16b6b41b --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCaseImpl.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.reactivex.rxjava3.core.Single + +internal class ConnectToQnnUseCaseImpl( + private val getConfigurationUseCase: GetConfigurationUseCase, + private val setServerConfigurationUseCase: SetServerConfigurationUseCase, + private val preferenceManager: PreferenceManager, +) : ConnectToQnnUseCase { + + override fun invoke(modelId: String, runOnCpu: Boolean): Single> = getConfigurationUseCase() + .map { originalConfiguration -> + preferenceManager.localQnnRunOnCpu = runOnCpu + originalConfiguration.copy( + source = ServerSource.LOCAL_QUALCOMM_QNN, + localQnnModelId = modelId, + ) + } + .flatMapCompletable(setServerConfigurationUseCase::invoke) + .andThen(Single.just(Result.success(Unit))) + .onErrorResumeNext { t -> Single.just(Result.failure(t)) } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCase.kt new file mode 100644 index 000000000..70b674408 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import io.reactivex.rxjava3.core.Single + +interface ConnectToStabilityAiUseCase { + operator fun invoke(apiKey: String): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImpl.kt similarity index 82% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImpl.kt index 400767c09..31e7e3a82 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCase +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCase import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import java.util.concurrent.TimeUnit diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCase.kt new file mode 100644 index 000000000..9fca50121 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import io.reactivex.rxjava3.core.Single + +interface ConnectToSwarmUiUseCase { + operator fun invoke(url: String, credentials: AuthorizationCredentials): Single> +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImpl.kt similarity index 81% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImpl.kt index 78d771c91..a59796121 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestSwarmUiConnectivityUseCase +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.usecase.connectivity.TestSwarmUiConnectivityUseCase import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import java.util.concurrent.TimeUnit diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCase.kt new file mode 100644 index 000000000..d3383063d --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.entity.Configuration +import io.reactivex.rxjava3.core.Single + +interface GetConfigurationUseCase { + operator fun invoke(): Single +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCaseImpl.kt similarity index 76% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCaseImpl.kt index e88ebd63a..3df91c433 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCaseImpl.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationStore -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationStore +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.reactivex.rxjava3.core.Single internal class GetConfigurationUseCaseImpl( @@ -23,11 +23,14 @@ internal class GetConfigurationUseCaseImpl( huggingFaceModel = preferenceManager.huggingFaceModel, stabilityAiApiKey = preferenceManager.stabilityAiApiKey, stabilityAiEngineId = preferenceManager.stabilityAiEngineId, + falAiApiKey = preferenceManager.falAiApiKey, authCredentials = authorizationStore.getAuthorizationCredentials(), localOnnxModelId = preferenceManager.localOnnxModelId, localOnnxModelPath = preferenceManager.localOnnxCustomModelPath, localMediaPipeModelId = preferenceManager.localMediaPipeModelId, localMediaPipeModelPath = preferenceManager.localMediaPipeCustomModelPath, + localQnnModelId = preferenceManager.localQnnModelId, + localQnnModelPath = preferenceManager.localQnnCustomModelPath, ) ) } diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCase.kt new file mode 100644 index 000000000..3d1c315ca --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.entity.Configuration +import io.reactivex.rxjava3.core.Completable + +interface SetServerConfigurationUseCase { + operator fun invoke(configuration: Configuration): Completable +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCaseImpl.kt similarity index 77% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCaseImpl.kt index aab29abc1..f3aff41fa 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCaseImpl.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationStore -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationStore +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.reactivex.rxjava3.core.Completable internal class SetServerConfigurationUseCaseImpl( @@ -24,9 +24,12 @@ internal class SetServerConfigurationUseCaseImpl( preferenceManager.huggingFaceModel = configuration.huggingFaceModel preferenceManager.stabilityAiApiKey = configuration.stabilityAiApiKey preferenceManager.stabilityAiEngineId = configuration.stabilityAiEngineId + preferenceManager.falAiApiKey = configuration.falAiApiKey preferenceManager.localOnnxModelId = configuration.localOnnxModelId preferenceManager.localOnnxCustomModelPath = configuration.localOnnxModelPath preferenceManager.localMediaPipeModelId = configuration.localMediaPipeModelId preferenceManager.localMediaPipeCustomModelPath = configuration.localMediaPipeModelPath + preferenceManager.localQnnModelId = configuration.localQnnModelId + preferenceManager.localQnnCustomModelPath = configuration.localQnnModelPath } } diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCase.kt similarity index 81% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCase.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCase.kt index 441a1f936..4c431f1cf 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCase.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCase.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.usecase.splash +package dev.minios.pdaiv1.domain.usecase.splash import io.reactivex.rxjava3.core.Single diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCaseImpl.kt similarity index 75% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCaseImpl.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCaseImpl.kt index 338f7a972..b3054bc1b 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCaseImpl.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCaseImpl.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.splash +package dev.minios.pdaiv1.domain.usecase.splash -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.splash.SplashNavigationUseCase.Action +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.splash.SplashNavigationUseCase.Action import io.reactivex.rxjava3.core.Single internal class SplashNavigationUseCaseImpl( diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCase.kt new file mode 100644 index 000000000..197e63553 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.stabilityai + +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine +import io.reactivex.rxjava3.core.Single + +interface FetchAndGetStabilityAiEnginesUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImpl.kt new file mode 100644 index 000000000..028eb48fc --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImpl.kt @@ -0,0 +1,27 @@ +package dev.minios.pdaiv1.domain.usecase.stabilityai + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.StabilityAiEnginesRepository +import io.reactivex.rxjava3.core.Single + +internal class FetchAndGetStabilityAiEnginesUseCaseImpl( + private val repository: StabilityAiEnginesRepository, + private val preferenceManager: PreferenceManager, +) : FetchAndGetStabilityAiEnginesUseCase { + + override fun invoke(): Single> { + if (preferenceManager.source != ServerSource.STABILITY_AI) { + return Single.just(emptyList()) + } + return repository + .fetchAndGet() + .flatMap { engines -> + if (!engines.map(StabilityAiEngine::id).contains(preferenceManager.stabilityAiEngineId)) { + preferenceManager.stabilityAiEngineId = engines.firstOrNull()?.id ?: "" + } + Single.just(engines) + } + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCase.kt new file mode 100644 index 000000000..87fc99dad --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCase.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.domain.usecase.stabilityai + +import io.reactivex.rxjava3.core.Flowable + +interface ObserveStabilityAiCreditsUseCase { + operator fun invoke(): Flowable +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImpl.kt new file mode 100644 index 000000000..7c59ca7eb --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImpl.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.domain.usecase.stabilityai + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.StabilityAiCreditsRepository +import io.reactivex.rxjava3.core.Flowable + +internal class ObserveStabilityAiCreditsUseCaseImpl( + private val repository: StabilityAiCreditsRepository, + private val preferenceManager: PreferenceManager, +) : ObserveStabilityAiCreditsUseCase { + + override fun invoke() = Flowable + .combineLatest( + preferenceManager.observe().map(Settings::source), + repository.fetchAndObserve(), + ::Pair, + ) + .map(Pair::second) + .onErrorReturn { 0f } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCase.kt new file mode 100644 index 000000000..81f18db51 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCase.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.domain.usecase.swarmmodel + +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import io.reactivex.rxjava3.core.Single + +interface FetchAndGetSwarmUiModelsUseCase { + operator fun invoke(): Single> +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImpl.kt new file mode 100644 index 000000000..3f3173005 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImpl.kt @@ -0,0 +1,27 @@ +package dev.minios.pdaiv1.domain.usecase.swarmmodel + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.SwarmUiModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.SwarmUiModelsRepository +import io.reactivex.rxjava3.core.Single + +internal class FetchAndGetSwarmUiModelsUseCaseImpl( + private val preferenceManager: PreferenceManager, + private val repository: SwarmUiModelsRepository, +) : FetchAndGetSwarmUiModelsUseCase { + + override fun invoke(): Single> { + if (preferenceManager.source != ServerSource.SWARM_UI) { + return Single.just(emptyList()) + } + return repository + .fetchAndGetModels() + .map { models -> + if (!models.map(SwarmUiModel::name).contains(preferenceManager.swarmUiModel)) { + preferenceManager.swarmUiModel = models.firstOrNull()?.name ?: "" + } + models + } + } +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCase.kt similarity index 79% rename from domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCase.kt rename to domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCase.kt index 5ad1c5a61..98c3f47b9 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCase.kt +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCase.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.usecase.wakelock +package dev.minios.pdaiv1.domain.usecase.wakelock interface AcquireWakelockUseCase { operator fun invoke(timeout: Long = DEFAULT_TIMEOUT): Result diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCaseImpl.kt new file mode 100644 index 000000000..6aa7c1629 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCaseImpl.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.domain.usecase.wakelock + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.repository.WakeLockRepository + +internal class AcquireWakelockUseCaseImpl( + private val wakeLockRepository: WakeLockRepository, +) : AcquireWakelockUseCase { + + override fun invoke(timeout: Long) = runCatching { + wakeLockRepository.wakeLock.acquire(timeout) + }.onFailure { t -> + errorLog(t) + } +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCase.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCase.kt new file mode 100644 index 000000000..9cc14eb02 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCase.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.domain.usecase.wakelock + +interface ReleaseWakeLockUseCase { + operator fun invoke(): Result +} diff --git a/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImpl.kt b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImpl.kt new file mode 100644 index 000000000..5c44d9258 --- /dev/null +++ b/domain/src/main/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImpl.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.domain.usecase.wakelock + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.repository.WakeLockRepository + +internal class ReleaseWakeLockUseCaseImpl( + private val wakeLockRepository: WakeLockRepository, +) : ReleaseWakeLockUseCase { + + override fun invoke() = runCatching { + wakeLockRepository.wakeLock.release() + }.onFailure { t -> + errorLog(t) + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/AiGenerationResultMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/AiGenerationResultMocks.kt deleted file mode 100644 index 8d51f0ae9..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/AiGenerationResultMocks.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import java.util.Date - -val mockAiGenerationResult = AiGenerationResult( - id = 5598L, - image = "img", - inputImage = "inp", - createdAt = Date(), - type = AiGenerationResult.Type.IMAGE_TO_IMAGE, - prompt = "prompt", - negativePrompt = "negative", - width = 512, - height = 512, - samplingSteps = 7, - cfgScale = 0.7f, - restoreFaces = true, - sampler = "sampler", - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - denoisingStrength = 1504f, - hidden = false, -) - -val mockAiGenerationResults = listOf(mockAiGenerationResult) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ConfigurationMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ConfigurationMocks.kt deleted file mode 100644 index da1d486ac..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ConfigurationMocks.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.Configuration - -val mockConfiguration = Configuration( - serverUrl = "http://5598.is.my.favorite.com", - swarmUiUrl = "http://5598.is.my.favorite.com", - swarmUiModel = "5598", - hordeApiKey = "5598", - openAiApiKey = "5598", - huggingFaceApiKey = "5598", - huggingFaceModel = "5598", - stabilityAiApiKey = "5598", - stabilityAiEngineId = "5598", - localOnnxModelId = "5598", - localOnnxModelPath = "/storage/emulated/0/5598", -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/HuggingFaceModelMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/HuggingFaceModelMocks.kt deleted file mode 100644 index d9e8e956f..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/HuggingFaceModelMocks.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel - -val mockHuggingFaceModels = listOf( - HuggingFaceModel.default, - HuggingFaceModel( - "80974f2d-7ee0-48e5-97bc-448de3c1d634", - "Analog Diffusion", - "wavymulder/Analog-Diffusion", - "https://huggingface.co/wavymulder/Analog-Diffusion", - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ImageToImagePayloadMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ImageToImagePayloadMocks.kt deleted file mode 100644 index d72aa0ff4..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ImageToImagePayloadMocks.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload - -val mockImageToImagePayload = ImageToImagePayload( - base64Image = "", - base64MaskImage = "", - denoisingStrength = 7f, - prompt = "prompt", - negativePrompt = "negative", - samplingSteps = 12, - cfgScale = 0.7f, - width = 512, - height = 512, - restoreFaces = true, - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - sampler = "sampler", - nsfw = true, - batchCount = 1, - inPaintingMaskInvert = 0, - inPaintFullResPadding = 0, - inPaintingFill = 0, - inPaintFullRes = false, - maskBlur = 0, - stabilityAiClipGuidance = null, - stabilityAiStylePreset = null, -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/LocalAiModelMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/LocalAiModelMocks.kt deleted file mode 100644 index 5ce3fa3b9..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/LocalAiModelMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel - -val mockLocalAiModels = listOf( - LocalAiModel.CustomOnnx, - LocalAiModel( - id = "1", - type = LocalAiModel.Type.ONNX, - name = "Model 1", - size = "5 Gb", - sources = listOf("https://example.com/1.html"), - downloaded = false, - selected = false, - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/LoraMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/LoraMocks.kt deleted file mode 100644 index 7e457a43b..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/LoraMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.LoRA - -val mockLoRAs = listOf( - LoRA( - name = "5598", - alias = "5598", - path = "/unknown", - ), - LoRA( - name = "151297", - alias = "151297", - path = "/unknown", - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/MediaStoreInfoMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/MediaStoreInfoMocks.kt deleted file mode 100644 index e3049220e..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/MediaStoreInfoMocks.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import android.net.Uri -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo - -val mockMediaStoreInfo = MediaStoreInfo( - count = 5598, - folderUri = Uri.EMPTY, -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ServerConfigurationMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ServerConfigurationMocks.kt deleted file mode 100644 index 9008a5bf6..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/ServerConfigurationMocks.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration - -val mockServerConfiguration = ServerConfiguration("checkpoint") diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StabilityAiEngineMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StabilityAiEngineMocks.kt deleted file mode 100644 index 330dad138..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StabilityAiEngineMocks.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine - -val mockStabilityAiEngines = listOf( - StabilityAiEngine( - id = "engine_1", - name = "Engine 1", - ), - StabilityAiEngine( - id = "engine_2", - name = "Engine 2", - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionEmbeddingMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionEmbeddingMocks.kt deleted file mode 100644 index 3a44c6329..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionEmbeddingMocks.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.Embedding - -val mockEmbeddings = listOf( - Embedding("embedding_1"), - Embedding("embedding_2"), - Embedding("embedding_3"), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionHyperNetworkMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionHyperNetworkMocks.kt deleted file mode 100644 index e7e697eba..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionHyperNetworkMocks.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork - -val mockStableDiffusionHyperNetworks = listOf( - StableDiffusionHyperNetwork( - name = "hyper_net_1", - path = "", - ), - StableDiffusionHyperNetwork( - name = "hyper_net_2", - path = "", - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionModelMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionModelMocks.kt deleted file mode 100644 index f55d4e38f..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionModelMocks.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel - -val mockStableDiffusionModels = listOf( - StableDiffusionModel( - title = "model", - modelName = "name", - hash = "hash", - sha256 = "sha256", - filename = "filename", - config = "config", - ), - StableDiffusionModel( - title = "checkpoint", - modelName = "checkpoint", - hash = "hash_2", - sha256 = "sha256_2", - filename = "filename_2", - config = "config_2", - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionSamplerMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionSamplerMocks.kt deleted file mode 100644 index 2cece698b..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/StableDiffusionSamplerMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler - -val mockStableDiffusionSamplers = listOf( - StableDiffusionSampler( - name = "sampler_1", - aliases = listOf("alias_1"), - options = mapOf("option" to "value"), - ), - StableDiffusionSampler( - name = "sampler_2", - aliases = listOf("alias_2"), - options = mapOf("option" to "value"), - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/SupporterMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/SupporterMocks.kt deleted file mode 100644 index 8d67f341e..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/SupporterMocks.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.Supporter -import java.util.Date - -val mockSupporters = listOf( - Supporter( - id = 5598, - name = "NZ", - date = Date(5598L), - message = "I always wanted support you ❤", - ), -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/TextToImagePayloadMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/TextToImagePayloadMocks.kt deleted file mode 100644 index 1817e0e5b..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/TextToImagePayloadMocks.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.domain.mocks - -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload - -val mockTextToImagePayload = TextToImagePayload( - prompt = "prompt", - negativePrompt = "negative", - samplingSteps = 12, - cfgScale = 0.7f, - width = 512, - height = 512, - restoreFaces = true, - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - sampler = "sampler", - nsfw = true, - batchCount = 1, - quality = null, - style = null, - openAiModel = null, - stabilityAiClipGuidance = null, - stabilityAiStylePreset = null, -) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCaseImplTest.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCaseImplTest.kt deleted file mode 100644 index 9a9209009..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/DataPreLoaderUseCaseImplTest.kt +++ /dev/null @@ -1,232 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.caching - -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.EmbeddingsRepository -import com.shifthackz.aisdv1.domain.repository.LorasRepository -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionHyperNetworksRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionModelsRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionSamplersRepository -import io.reactivex.rxjava3.core.Completable -import org.junit.Test - -class DataPreLoaderUseCaseImplTest { - - private val stubServerConfigurationRepository = mock() - private val stubStableDiffusionModelsRepository = mock() - private val stubStableDiffusionSamplersRepository = mock() - private val stubLorasRepository = mock() - private val stubStableDiffusionHyperNetworksRepository = mock() - private val stubEmbeddingsRepository = mock() - - private val useCase = DataPreLoaderUseCaseImpl( - serverConfigurationRepository = stubServerConfigurationRepository, - sdModelsRepository = stubStableDiffusionModelsRepository, - sdSamplersRepository = stubStableDiffusionSamplersRepository, - sdLorasRepository = stubLorasRepository, - sdHyperNetworksRepository = stubStableDiffusionHyperNetworksRepository, - sdEmbeddingsRepository = stubEmbeddingsRepository, - ) - - @Test - fun `given all data fetched successfully, expected complete value`() { - whenever(stubServerConfigurationRepository.fetchConfiguration()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionModelsRepository.fetchModels()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) - .thenReturn(Completable.complete()) - - whenever(stubLorasRepository.fetchLoras()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) - .thenReturn(Completable.complete()) - - whenever(stubEmbeddingsRepository.fetchEmbeddings()) - .thenReturn(Completable.complete()) - - useCase() - .test() - .assertNoErrors() - .await() - .assertComplete() - } - - @Test - fun `given configuration fetch failed, expected error value`() { - val stubException = Throwable("Can not fetch configuration.") - - whenever(stubServerConfigurationRepository.fetchConfiguration()) - .thenReturn(Completable.error(stubException)) - - whenever(stubStableDiffusionModelsRepository.fetchModels()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) - .thenReturn(Completable.complete()) - - whenever(stubLorasRepository.fetchLoras()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) - .thenReturn(Completable.complete()) - - whenever(stubEmbeddingsRepository.fetchEmbeddings()) - .thenReturn(Completable.complete()) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given models fetch failed, expected error value`() { - val stubException = Throwable("Can not fetch models.") - - whenever(stubServerConfigurationRepository.fetchConfiguration()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionModelsRepository.fetchModels()) - .thenReturn(Completable.error(stubException)) - - whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) - .thenReturn(Completable.complete()) - - whenever(stubLorasRepository.fetchLoras()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) - .thenReturn(Completable.complete()) - - whenever(stubEmbeddingsRepository.fetchEmbeddings()) - .thenReturn(Completable.complete()) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given samplers fetch failed, expected error value`() { - val stubException = Throwable("Can not fetch samplers.") - - whenever(stubServerConfigurationRepository.fetchConfiguration()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionModelsRepository.fetchModels()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) - .thenReturn(Completable.error(stubException)) - - whenever(stubLorasRepository.fetchLoras()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) - .thenReturn(Completable.complete()) - - whenever(stubEmbeddingsRepository.fetchEmbeddings()) - .thenReturn(Completable.complete()) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given loras fetch failed, expected error value`() { - val stubException = Throwable("Can not fetch loras.") - - whenever(stubServerConfigurationRepository.fetchConfiguration()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionModelsRepository.fetchModels()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) - .thenReturn(Completable.complete()) - - whenever(stubLorasRepository.fetchLoras()) - .thenReturn(Completable.error(stubException)) - - whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) - .thenReturn(Completable.complete()) - - whenever(stubEmbeddingsRepository.fetchEmbeddings()) - .thenReturn(Completable.complete()) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given hypernetworks fetch failed, expected error value`() { - val stubException = Throwable("Can not fetch hypernetworks.") - - whenever(stubServerConfigurationRepository.fetchConfiguration()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionModelsRepository.fetchModels()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) - .thenReturn(Completable.complete()) - - whenever(stubLorasRepository.fetchLoras()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) - .thenReturn(Completable.error(stubException)) - - whenever(stubEmbeddingsRepository.fetchEmbeddings()) - .thenReturn(Completable.complete()) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given embeddings fetch failed, expected error value`() { - val stubException = Throwable("Can not fetch embeddings.") - - whenever(stubServerConfigurationRepository.fetchConfiguration()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionModelsRepository.fetchModels()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) - .thenReturn(Completable.complete()) - - whenever(stubLorasRepository.fetchLoras()) - .thenReturn(Completable.complete()) - - whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) - .thenReturn(Completable.complete()) - - whenever(stubEmbeddingsRepository.fetchEmbeddings()) - .thenReturn(Completable.error(stubException)) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } -} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImplTest.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImplTest.kt deleted file mode 100644 index 77da17373..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImplTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.huggingface - -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockHuggingFaceModels -import com.shifthackz.aisdv1.domain.repository.HuggingFaceModelsRepository -import io.reactivex.rxjava3.core.Single -import org.junit.Test - -class FetchAndGetHuggingFaceModelsUseCaseImplTest { - - private val stubRepository = mock() - - private val useCase = FetchAndGetHuggingFaceModelsUseCaseImpl(stubRepository) - - @Test - fun `given repository provided models list, expected valid list value`() { - whenever(stubRepository.fetchAndGetHuggingFaceModels()) - .thenReturn(Single.just(mockHuggingFaceModels)) - - useCase() - .test() - .assertNoErrors() - .assertValue(mockHuggingFaceModels) - .await() - .assertComplete() - } - - @Test - fun `given repository provided empty models list, expected empty list value`() { - whenever(stubRepository.fetchAndGetHuggingFaceModels()) - .thenReturn(Single.just(emptyList())) - - useCase() - .test() - .assertNoErrors() - .assertValue(emptyList()) - .await() - .assertComplete() - } - - @Test - fun `given repository thrown exception, expected error value`() { - val stubException = Throwable("Unknown error occurred.") - - whenever(stubRepository.fetchAndGetHuggingFaceModels()) - .thenReturn(Single.error(stubException)) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } -} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImplTest.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImplTest.kt deleted file mode 100644 index 6d6450b03..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImplTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.sdmodel - -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.ServerConfiguration -import com.shifthackz.aisdv1.domain.mocks.mockServerConfiguration -import com.shifthackz.aisdv1.domain.mocks.mockStableDiffusionModels -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionModelsRepository -import io.reactivex.rxjava3.core.Single -import org.junit.Assert -import org.junit.Test - -class GetStableDiffusionModelsUseCaseImplTest { - - private val stubServerConfigurationRepository = mock() - private val stubSdModelsRepository = mock() - - private val useCase = GetStableDiffusionModelsUseCaseImpl( - serverConfigurationRepository = stubServerConfigurationRepository, - sdModelsRepository = stubSdModelsRepository, - ) - - @Test - fun `given repository returns list with value present in configuration, expected list with selected value`() { - whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) - .thenReturn(Single.just(mockServerConfiguration)) - - whenever(stubSdModelsRepository.fetchAndGetModels()) - .thenReturn(Single.just(mockStableDiffusionModels)) - - val expectedValue = mockStableDiffusionModels.map { - it to (it.title == mockServerConfiguration.sdModelCheckpoint) - } - - useCase() - .test() - .assertNoErrors() - .assertValue(expectedValue) - .also { - Assert.assertEquals( - true, - expectedValue.any { (_, selected) -> selected }, - ) - } - .await() - .assertComplete() - } - - @Test - fun `given repository returns list with no value present in configuration, expected list without selected value`() { - val stubServerConfiguration = ServerConfiguration("nonsense") - - whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) - .thenReturn(Single.just(stubServerConfiguration)) - - whenever(stubSdModelsRepository.fetchAndGetModels()) - .thenReturn(Single.just(mockStableDiffusionModels)) - - val expectedValue = mockStableDiffusionModels.map { - it to (it.title == stubServerConfiguration.sdModelCheckpoint) - } - - useCase() - .test() - .assertNoErrors() - .assertValue(expectedValue) - .also { - Assert.assertEquals( - true, - !expectedValue.any { (_, selected) -> selected }, - ) - } - .await() - .assertComplete() - } - - @Test - fun `given exception while fetching configuration, expected error value`() { - val stubException = Throwable("Network error.") - - whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) - .thenReturn(Single.error(stubException)) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } - - @Test - fun `given exception while fetching models, expected error value`() { - val stubException = Throwable("Network error.") - - whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) - .thenReturn(Single.just(mockServerConfiguration)) - - whenever(stubSdModelsRepository.fetchAndGetModels()) - .thenReturn(Single.error(stubException)) - - useCase() - .test() - .assertError(stubException) - .await() - .assertNotComplete() - } -} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImplTest.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImplTest.kt deleted file mode 100644 index 68636239d..000000000 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImplTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.shifthackz.aisdv1.domain.usecase.stabilityai - -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockStabilityAiEngines -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.StabilityAiEnginesRepository -import io.reactivex.rxjava3.core.Single -import org.junit.Test - -class FetchAndGetStabilityAiEnginesUseCaseImplTest { - - private val stubRepository = mock() - - private val stubPreferenceManager = mock() - - private val useCase = FetchAndGetStabilityAiEnginesUseCaseImpl( - repository = stubRepository, - preferenceManager = stubPreferenceManager, - ) - - @Test - fun `given repository returned engines list, id present in preference, expected the same engines list, id not changed`() { - whenever(stubRepository.fetchAndGet()) - .thenReturn(Single.just(mockStabilityAiEngines)) - - whenever(stubPreferenceManager::stabilityAiEngineId.get()) - .thenReturn("engine_1") - - useCase() - .test() - .assertNoErrors() - .assertValue(mockStabilityAiEngines) - .assertComplete() - } - - @Test - fun `given repository thrown exception, expected the same exception`() { - val stubException = Throwable("Network exception") - - whenever(stubRepository.fetchAndGet()) - .thenReturn(Single.error(stubException)) - - useCase() - .test() - .assertError(stubException) - .assertNotComplete() - } -} diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/AiGenerationResultMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/AiGenerationResultMocks.kt new file mode 100644 index 000000000..ae32d28df --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/AiGenerationResultMocks.kt @@ -0,0 +1,32 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.MediaType +import java.util.Date + +val mockAiGenerationResult = AiGenerationResult( + id = 5598L, + image = "img", + inputImage = "inp", + createdAt = Date(), + type = AiGenerationResult.Type.IMAGE_TO_IMAGE, + prompt = "prompt", + negativePrompt = "negative", + width = 512, + height = 512, + samplingSteps = 7, + cfgScale = 0.7f, + restoreFaces = true, + sampler = "sampler", + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + denoisingStrength = 1504f, + hidden = false, + mediaPath = "", + inputMediaPath = "", + mediaType = MediaType.IMAGE, + modelName = "MockModel", +) + +val mockAiGenerationResults = listOf(mockAiGenerationResult) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ConfigurationMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ConfigurationMocks.kt new file mode 100644 index 000000000..4704bb6cc --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ConfigurationMocks.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.Configuration + +val mockConfiguration = Configuration( + serverUrl = "http://5598.is.my.favorite.com", + swarmUiUrl = "http://5598.is.my.favorite.com", + swarmUiModel = "5598", + hordeApiKey = "5598", + openAiApiKey = "5598", + huggingFaceApiKey = "5598", + huggingFaceModel = "5598", + stabilityAiApiKey = "5598", + stabilityAiEngineId = "5598", + falAiApiKey = "5598", + localOnnxModelId = "5598", + localOnnxModelPath = "/storage/emulated/0/5598", + localMediaPipeModelId = "5598", + localMediaPipeModelPath = "/storage/emulated/0/5598", + localQnnModelId = "5598", + localQnnModelPath = "/storage/emulated/0/5598", +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/FalAiMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/FalAiMocks.kt new file mode 100644 index 000000000..2147f80de --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/FalAiMocks.kt @@ -0,0 +1,53 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiEndpointCategory +import dev.minios.pdaiv1.domain.entity.FalAiEndpointSchema +import dev.minios.pdaiv1.domain.entity.FalAiInputProperty +import dev.minios.pdaiv1.domain.entity.FalAiPayload +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType + +val mockFalAiInputProperty = FalAiInputProperty( + name = "prompt", + title = "Prompt", + description = "The prompt to generate an image from", + type = FalAiPropertyType.STRING, + default = null, + minimum = null, + maximum = null, + enumValues = null, + isRequired = true, + isImageInput = false, +) + +val mockFalAiEndpointSchema = FalAiEndpointSchema( + baseUrl = "https://queue.fal.run", + submissionPath = "/fal-ai/flux/schnell", + inputProperties = listOf(mockFalAiInputProperty), + requiredProperties = listOf("prompt"), + propertyOrder = listOf("prompt"), +) + +val mockFalAiEndpoint = FalAiEndpoint( + id = "fal-ai/flux/schnell", + endpointId = "fal-ai/flux/schnell", + title = "FLUX.1 [schnell]", + description = "Fast text to image generation", + category = FalAiEndpointCategory.TEXT_TO_IMAGE, + group = "FLUX", + thumbnailUrl = "https://fal.ai/thumbnails/flux-schnell.jpg", + playgroundUrl = "https://fal.ai/models/fal-ai/flux/schnell", + documentationUrl = "https://fal.ai/models/fal-ai/flux/schnell/api", + isCustom = false, + schema = mockFalAiEndpointSchema, +) + +val mockFalAiEndpoints = listOf(mockFalAiEndpoint) + +val mockFalAiPayload = FalAiPayload( + endpointId = "fal-ai/flux/schnell", + parameters = mapOf( + "prompt" to "a beautiful sunset", + "num_inference_steps" to 4, + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ForgeModuleMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ForgeModuleMocks.kt new file mode 100644 index 000000000..62b01086d --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ForgeModuleMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.ForgeModule + +val mockForgeModule = ForgeModule( + name = "ADetailer", + path = "extensions/adetailer", +) + +val mockForgeModules = listOf( + mockForgeModule, + ForgeModule( + name = "ControlNet", + path = "extensions/sd-webui-controlnet", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/HuggingFaceModelMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/HuggingFaceModelMocks.kt new file mode 100644 index 000000000..96f8e6dfb --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/HuggingFaceModelMocks.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel + +val mockHuggingFaceModels = listOf( + HuggingFaceModel.default, + HuggingFaceModel( + "80974f2d-7ee0-48e5-97bc-448de3c1d634", + "Analog Diffusion", + "wavymulder/Analog-Diffusion", + "https://huggingface.co/wavymulder/Analog-Diffusion", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ImageToImagePayloadMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ImageToImagePayloadMocks.kt new file mode 100644 index 000000000..18e735957 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ImageToImagePayloadMocks.kt @@ -0,0 +1,29 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload + +val mockImageToImagePayload = ImageToImagePayload( + base64Image = "", + base64MaskImage = "", + denoisingStrength = 7f, + prompt = "prompt", + negativePrompt = "negative", + samplingSteps = 12, + cfgScale = 0.7f, + width = 512, + height = 512, + restoreFaces = true, + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + sampler = "sampler", + nsfw = true, + batchCount = 1, + inPaintingMaskInvert = 0, + inPaintFullResPadding = 0, + inPaintingFill = 0, + inPaintFullRes = false, + maskBlur = 0, + stabilityAiClipGuidance = null, + stabilityAiStylePreset = null, +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/LocalAiModelMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/LocalAiModelMocks.kt new file mode 100644 index 000000000..59dd55762 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/LocalAiModelMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.LocalAiModel + +val mockLocalAiModels = listOf( + LocalAiModel.CustomOnnx, + LocalAiModel( + id = "1", + type = LocalAiModel.Type.ONNX, + name = "Model 1", + size = "5 Gb", + sources = listOf("https://example.com/1.html"), + downloaded = false, + selected = false, + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/LoraMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/LoraMocks.kt new file mode 100644 index 000000000..e0ea2fd68 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/LoraMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.LoRA + +val mockLoRAs = listOf( + LoRA( + name = "5598", + alias = "5598", + path = "/unknown", + ), + LoRA( + name = "151297", + alias = "151297", + path = "/unknown", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/MediaStoreInfoMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/MediaStoreInfoMocks.kt new file mode 100644 index 000000000..55317a0c1 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/MediaStoreInfoMocks.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.mocks + +import android.net.Uri +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo + +val mockMediaStoreInfo = MediaStoreInfo( + count = 5598, + folderUri = Uri.EMPTY, +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ServerConfigurationMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ServerConfigurationMocks.kt new file mode 100644 index 000000000..9aed829fa --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/ServerConfigurationMocks.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.ServerConfiguration + +val mockServerConfiguration = ServerConfiguration("checkpoint") diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/SettingsMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SettingsMocks.kt similarity index 77% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/SettingsMocks.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SettingsMocks.kt index 4dfca8632..09decdaa3 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/SettingsMocks.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SettingsMocks.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.mocks +package dev.minios.pdaiv1.domain.mocks -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.Settings val mockSettings = Settings( serverUrl = "", diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StabilityAiEngineMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StabilityAiEngineMocks.kt new file mode 100644 index 000000000..f9be485a8 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StabilityAiEngineMocks.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine + +val mockStabilityAiEngines = listOf( + StabilityAiEngine( + id = "engine_1", + name = "Engine 1", + ), + StabilityAiEngine( + id = "engine_2", + name = "Engine 2", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionEmbeddingMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionEmbeddingMocks.kt new file mode 100644 index 000000000..bc1d3edcf --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionEmbeddingMocks.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.Embedding + +val mockEmbeddings = listOf( + Embedding("embedding_1"), + Embedding("embedding_2"), + Embedding("embedding_3"), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionHyperNetworkMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionHyperNetworkMocks.kt new file mode 100644 index 000000000..1ac500a4b --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionHyperNetworkMocks.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork + +val mockStableDiffusionHyperNetworks = listOf( + StableDiffusionHyperNetwork( + name = "hyper_net_1", + path = "", + ), + StableDiffusionHyperNetwork( + name = "hyper_net_2", + path = "", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionModelMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionModelMocks.kt new file mode 100644 index 000000000..4bce906ae --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionModelMocks.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel + +val mockStableDiffusionModels = listOf( + StableDiffusionModel( + title = "model", + modelName = "name", + hash = "hash", + sha256 = "sha256", + filename = "filename", + config = "config", + ), + StableDiffusionModel( + title = "checkpoint", + modelName = "checkpoint", + hash = "hash_2", + sha256 = "sha256_2", + filename = "filename_2", + config = "config_2", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionSamplerMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionSamplerMocks.kt new file mode 100644 index 000000000..bcb3c551c --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/StableDiffusionSamplerMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler + +val mockStableDiffusionSamplers = listOf( + StableDiffusionSampler( + name = "sampler_1", + aliases = listOf("alias_1"), + options = mapOf("option" to "value"), + ), + StableDiffusionSampler( + name = "sampler_2", + aliases = listOf("alias_2"), + options = mapOf("option" to "value"), + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SupporterMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SupporterMocks.kt new file mode 100644 index 000000000..34c5517db --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SupporterMocks.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.Supporter +import java.util.Date + +val mockSupporters = listOf( + Supporter( + id = 5598, + name = "NZ", + date = Date(5598L), + message = "I always wanted support you ❤", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SwarmUiModelMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SwarmUiModelMocks.kt new file mode 100644 index 000000000..3e4aa26ba --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/SwarmUiModelMocks.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.SwarmUiModel + +val mockSwarmUiModel = SwarmUiModel( + name = "mock-model", + title = "Mock Model", + author = "Test Author", +) + +val mockSwarmUiModels = listOf( + mockSwarmUiModel, + SwarmUiModel( + name = "another-model", + title = "Another Model", + author = "Another Author", + ), +) diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/TextToImagePayloadMocks.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/TextToImagePayloadMocks.kt new file mode 100644 index 000000000..16765a025 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/mocks/TextToImagePayloadMocks.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.domain.mocks + +import dev.minios.pdaiv1.domain.entity.TextToImagePayload + +val mockTextToImagePayload = TextToImagePayload( + prompt = "prompt", + negativePrompt = "negative", + samplingSteps = 12, + cfgScale = 0.7f, + width = 512, + height = 512, + restoreFaces = true, + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + sampler = "sampler", + nsfw = true, + batchCount = 1, + quality = null, + style = null, + openAiModel = null, + stabilityAiClipGuidance = null, + stabilityAiStylePreset = null, +) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCaseImplTest.kt index 5a6dc4bff..89516e072 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/ClearAppCacheUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/ClearAppCacheUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.caching +package dev.minios.pdaiv1.domain.usecase.caching -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.FileLoggingTree -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.FileLoggingTree +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCaseImplTest.kt new file mode 100644 index 000000000..8da4508b2 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/DataPreLoaderUseCaseImplTest.kt @@ -0,0 +1,296 @@ +package dev.minios.pdaiv1.domain.usecase.caching + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.EmbeddingsRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.LorasRepository +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionHyperNetworksRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionModelsRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionSamplersRepository +import io.reactivex.rxjava3.core.Completable +import org.junit.Test + +class DataPreLoaderUseCaseImplTest { + + private val stubPreferenceManager = mock() + private val stubServerConfigurationRepository = mock() + private val stubStableDiffusionModelsRepository = mock() + private val stubStableDiffusionSamplersRepository = mock() + private val stubLorasRepository = mock() + private val stubStableDiffusionHyperNetworksRepository = mock() + private val stubEmbeddingsRepository = mock() + private val stubGenerationResultRepository = mock() + + private val useCase = DataPreLoaderUseCaseImpl( + preferenceManager = stubPreferenceManager, + serverConfigurationRepository = stubServerConfigurationRepository, + sdModelsRepository = stubStableDiffusionModelsRepository, + sdSamplersRepository = stubStableDiffusionSamplersRepository, + sdLorasRepository = stubLorasRepository, + sdHyperNetworksRepository = stubStableDiffusionHyperNetworksRepository, + sdEmbeddingsRepository = stubEmbeddingsRepository, + generationResultRepository = stubGenerationResultRepository, + ) + + @Test + fun `given all data fetched successfully, source is AUTOMATIC1111, expected complete value`() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + whenever(stubServerConfigurationRepository.fetchConfiguration()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionModelsRepository.fetchModels()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) + .thenReturn(Completable.complete()) + + whenever(stubLorasRepository.fetchLoras()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) + .thenReturn(Completable.complete()) + + whenever(stubEmbeddingsRepository.fetchEmbeddings()) + .thenReturn(Completable.complete()) + + useCase() + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given source is FAL_AI, expected only migration runs and completes`() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.FAL_AI) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + useCase() + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given configuration fetch failed, expected error value`() { + val stubException = Throwable("Can not fetch configuration.") + + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + whenever(stubServerConfigurationRepository.fetchConfiguration()) + .thenReturn(Completable.error(stubException)) + + whenever(stubStableDiffusionModelsRepository.fetchModels()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) + .thenReturn(Completable.complete()) + + whenever(stubLorasRepository.fetchLoras()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) + .thenReturn(Completable.complete()) + + whenever(stubEmbeddingsRepository.fetchEmbeddings()) + .thenReturn(Completable.complete()) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given models fetch failed, expected error value`() { + val stubException = Throwable("Can not fetch models.") + + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + whenever(stubServerConfigurationRepository.fetchConfiguration()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionModelsRepository.fetchModels()) + .thenReturn(Completable.error(stubException)) + + whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) + .thenReturn(Completable.complete()) + + whenever(stubLorasRepository.fetchLoras()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) + .thenReturn(Completable.complete()) + + whenever(stubEmbeddingsRepository.fetchEmbeddings()) + .thenReturn(Completable.complete()) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given samplers fetch failed, expected error value`() { + val stubException = Throwable("Can not fetch samplers.") + + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + whenever(stubServerConfigurationRepository.fetchConfiguration()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionModelsRepository.fetchModels()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) + .thenReturn(Completable.error(stubException)) + + whenever(stubLorasRepository.fetchLoras()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) + .thenReturn(Completable.complete()) + + whenever(stubEmbeddingsRepository.fetchEmbeddings()) + .thenReturn(Completable.complete()) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given loras fetch failed, expected error value`() { + val stubException = Throwable("Can not fetch loras.") + + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + whenever(stubServerConfigurationRepository.fetchConfiguration()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionModelsRepository.fetchModels()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) + .thenReturn(Completable.complete()) + + whenever(stubLorasRepository.fetchLoras()) + .thenReturn(Completable.error(stubException)) + + whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) + .thenReturn(Completable.complete()) + + whenever(stubEmbeddingsRepository.fetchEmbeddings()) + .thenReturn(Completable.complete()) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given hypernetworks fetch failed, expected error value`() { + val stubException = Throwable("Can not fetch hypernetworks.") + + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + whenever(stubServerConfigurationRepository.fetchConfiguration()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionModelsRepository.fetchModels()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) + .thenReturn(Completable.complete()) + + whenever(stubLorasRepository.fetchLoras()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) + .thenReturn(Completable.error(stubException)) + + whenever(stubEmbeddingsRepository.fetchEmbeddings()) + .thenReturn(Completable.complete()) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given embeddings fetch failed, expected error value`() { + val stubException = Throwable("Can not fetch embeddings.") + + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + + whenever(stubGenerationResultRepository.migrateBase64ToFiles()) + .thenReturn(Completable.complete()) + + whenever(stubServerConfigurationRepository.fetchConfiguration()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionModelsRepository.fetchModels()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionSamplersRepository.fetchSamplers()) + .thenReturn(Completable.complete()) + + whenever(stubLorasRepository.fetchLoras()) + .thenReturn(Completable.complete()) + + whenever(stubStableDiffusionHyperNetworksRepository.fetchHyperNetworks()) + .thenReturn(Completable.complete()) + + whenever(stubEmbeddingsRepository.fetchEmbeddings()) + .thenReturn(Completable.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImplTest.kt similarity index 84% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImplTest.kt index 3d275385e..e1d3aaeb7 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/GetLastResultFromCacheUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.caching +package dev.minios.pdaiv1.domain.usecase.caching import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.domain.repository.TemporaryGenerationResultRepository +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.domain.repository.TemporaryGenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImplTest.kt index 7507d5f1e..12feced7e 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/caching/SaveLastResultToCacheUseCaseImplTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.domain.usecase.caching +package dev.minios.pdaiv1.domain.usecase.caching import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.TemporaryGenerationResultRepository +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.TemporaryGenerationResultRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImplTest.kt similarity index 94% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImplTest.kt index 777ade05b..35a063bea 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/ObserveSeverConnectivityUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity +package dev.minios.pdaiv1.domain.usecase.connectivity import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.gateway.ServerConnectivityGateway +import dev.minios.pdaiv1.domain.gateway.ServerConnectivityGateway import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.BehaviorSubject diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImplTest.kt index 741e5e341..e59f85428 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/PingStableDiffusionServiceUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity +package dev.minios.pdaiv1.domain.usecase.connectivity import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCaseImplTest.kt similarity index 90% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCaseImplTest.kt index 6d5ffcc1b..a43a718fe 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestConnectivityUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestConnectivityUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity +package dev.minios.pdaiv1.domain.usecase.connectivity import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCaseImplTest.kt new file mode 100644 index 000000000..637d541e6 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestFalAiApiKeyUseCaseImplTest.kt @@ -0,0 +1,56 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class TestFalAiApiKeyUseCaseImplTest { + + private val stubException = Throwable("Failed to validate API key.") + private val stubFalAiGenerationRepository = mock() + + private val useCase = TestFalAiApiKeyUseCaseImpl( + falAiGenerationRepository = stubFalAiGenerationRepository, + ) + + @Test + fun `given attempt to validate api key, repository returns true, expected true value`() { + whenever(stubFalAiGenerationRepository.validateApiKey()) + .thenReturn(Single.just(true)) + + useCase() + .test() + .assertNoErrors() + .assertValue(true) + .await() + .assertComplete() + } + + @Test + fun `given attempt to validate api key, repository returns false, expected false value`() { + whenever(stubFalAiGenerationRepository.validateApiKey()) + .thenReturn(Single.just(false)) + + useCase() + .test() + .assertNoErrors() + .assertValue(false) + .await() + .assertComplete() + } + + @Test + fun `given attempt to validate api key, repository throws exception, expected error value`() { + whenever(stubFalAiGenerationRepository.validateApiKey()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImplTest.kt index 769d5f36a..7162a9843 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHordeApiKeyUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity +package dev.minios.pdaiv1.domain.usecase.connectivity import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImplTest.kt index 10d4a46cc..3c815b607 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestHuggingFaceApiKeyUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity +package dev.minios.pdaiv1.domain.usecase.connectivity import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImplTest.kt index 40877cc82..8455a7efa 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestOpenAiApiKeyUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity +package dev.minios.pdaiv1.domain.usecase.connectivity import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.OpenAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.OpenAiGenerationRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImplTest.kt index 23a1d673e..b59f08925 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestStabilityAiApiKeyUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.connectivity +package dev.minios.pdaiv1.domain.usecase.connectivity import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImplTest.kt new file mode 100644 index 000000000..424afe1f1 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/connectivity/TestSwarmUiConnectivityUseCaseImplTest.kt @@ -0,0 +1,43 @@ +package dev.minios.pdaiv1.domain.usecase.connectivity + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository +import io.reactivex.rxjava3.core.Completable +import org.junit.Test + +class TestSwarmUiConnectivityUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = TestSwarmUiConnectivityUseCaseImpl(stubRepository) + + @Test + fun `given repository check successful, expected complete`() { + val url = "http://localhost:7801" + + whenever(stubRepository.checkApiAvailability(url)) + .thenReturn(Completable.complete()) + + useCase(url) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given repository check failed, expected error`() { + val url = "http://localhost:7801" + val stubException = Throwable("Connection refused") + + whenever(stubRepository.checkApiAvailability(url)) + .thenReturn(Completable.error(stubException)) + + useCase(url) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImplTest.kt similarity index 89% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImplTest.kt index 1d56c81b4..c24a05642 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.debug +package dev.minios.pdaiv1.domain.usecase.debug import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImplTest.kt similarity index 89% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImplTest.kt index a838d0c3c..4f8e0deff 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/donate/FetchAndGetSupportersUseCaseImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.usecase.donate +package dev.minios.pdaiv1.domain.usecase.donate -import com.shifthackz.aisdv1.domain.mocks.mockSupporters -import com.shifthackz.aisdv1.domain.repository.SupportersRepository +import dev.minios.pdaiv1.domain.mocks.mockSupporters +import dev.minios.pdaiv1.domain.repository.SupportersRepository import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCaseImplTest.kt index f3504939a..291d9fc96 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DeleteModelUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/DeleteModelUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable +package dev.minios.pdaiv1.domain.usecase.downloadable import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt similarity index 90% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt index f5c463a75..885d564b9 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable +package dev.minios.pdaiv1.domain.usecase.downloadable import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.PublishSubject import org.junit.Before @@ -29,7 +29,7 @@ class DownloadModelUseCaseImplTest { @Test fun `given download running, then finishes successfully, expected final state is Complete`() { - val stubObserver = useCase("5598", "https://moroz.cc/stub.zip").test() + val stubObserver = useCase("5598", "https://example.com/stub.zip").test() stubDownloadStatus.onNext(DownloadState.Unknown) @@ -58,7 +58,7 @@ class DownloadModelUseCaseImplTest { @Test fun `given download running, then fails, expected final state is Error`() { - val stubObserver = useCase("5598", "https://moroz.cc/stub.zip").test() + val stubObserver = useCase("5598", "https://example.com/stub.zip").test() stubDownloadStatus.onNext(DownloadState.Unknown) @@ -87,7 +87,7 @@ class DownloadModelUseCaseImplTest { @Test fun `given download running, then fails, then user restarts download, then completes, expected state Error on 1st try, final state is Complete`() { - val stubObserver = useCase("5598", "https://moroz.cc/stub.zip").test() + val stubObserver = useCase("5598", "https://example.com/stub.zip").test() stubDownloadStatus.onNext(DownloadState.Unknown) @@ -143,7 +143,7 @@ class DownloadModelUseCaseImplTest { whenever(stubRepository.download(any(), any())) .thenReturn(Observable.error(stubTerminateException)) - useCase("5598", "https://moroz.cc/stub.zip") + useCase("5598", "https://example.com/stub.zip") .test() .assertError(stubTerminateException) .await() diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImplTest.kt new file mode 100644 index 000000000..f4378a1f7 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalMediaPipeModelsUseCaseImplTest.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.mocks.mockLocalAiModels +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class GetLocalMediaPipeModelsUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = GetLocalMediaPipeModelsUseCaseImpl(stubRepository) + + @Test + fun `given repository returned models list, expected valid models list value`() { + whenever(stubRepository.getAllMediaPipe()) + .thenReturn(Single.just(mockLocalAiModels)) + + useCase() + .test() + .assertNoErrors() + .assertValue(mockLocalAiModels) + .await() + .assertComplete() + } + + @Test + fun `given repository returned empty models list, expected empty models list value`() { + whenever(stubRepository.getAllMediaPipe()) + .thenReturn(Single.just(emptyList())) + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given repository thrown exception, expected error value`() { + val stubException = Throwable("Unable to collect local models.") + + whenever(stubRepository.getAllMediaPipe()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCaseImplTest.kt new file mode 100644 index 000000000..0f828005e --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalModelUseCaseImplTest.kt @@ -0,0 +1,52 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.datasource.DownloadableModelDataSource +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class GetLocalModelUseCaseImplTest { + + private val stubLocalDataSource = mock() + + private val useCase = GetLocalModelUseCaseImpl(stubLocalDataSource) + + private val stubModel = LocalAiModel( + id = "test-model-id", + type = LocalAiModel.Type.ONNX, + name = "Test Model", + size = "5 GB", + sources = listOf("https://example.com/model"), + downloaded = true, + selected = false, + ) + + @Test + fun `given local data source returned model, expected valid model value`() { + whenever(stubLocalDataSource.getById("test-model-id")) + .thenReturn(Single.just(stubModel)) + + useCase("test-model-id") + .test() + .assertNoErrors() + .assertValue(stubModel) + .await() + .assertComplete() + } + + @Test + fun `given local data source thrown exception, expected error value`() { + val stubException = Throwable("Model not found.") + + whenever(stubLocalDataSource.getById("nonexistent-id")) + .thenReturn(Single.error(stubException)) + + useCase("nonexistent-id") + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImplTest.kt index d154c1633..7a236281f 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalOnnxModelsUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable +package dev.minios.pdaiv1.domain.usecase.downloadable import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockLocalAiModels -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository +import dev.minios.pdaiv1.domain.mocks.mockLocalAiModels +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCaseImplTest.kt new file mode 100644 index 000000000..5039b2534 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/GetLocalQnnModelsUseCaseImplTest.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.mocks.mockLocalAiModels +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class GetLocalQnnModelsUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = GetLocalQnnModelsUseCaseImpl(stubRepository) + + @Test + fun `given repository returned models list, expected valid models list value`() { + whenever(stubRepository.getAllQnn()) + .thenReturn(Single.just(mockLocalAiModels)) + + useCase() + .test() + .assertNoErrors() + .assertValue(mockLocalAiModels) + .await() + .assertComplete() + } + + @Test + fun `given repository returned empty models list, expected empty models list value`() { + whenever(stubRepository.getAllQnn()) + .thenReturn(Single.just(emptyList())) + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given repository thrown exception, expected error value`() { + val stubException = Throwable("Unable to collect local models.") + + whenever(stubRepository.getAllQnn()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImplTest.kt similarity index 92% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImplTest.kt index a61684b1a..80e1b7d09 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/ObserveLocalOnnxModelsUseCaseImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.downloadable +package dev.minios.pdaiv1.domain.usecase.downloadable import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.mocks.mockLocalAiModels -import com.shifthackz.aisdv1.domain.repository.DownloadableModelRepository +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.mocks.mockLocalAiModels +import dev.minios.pdaiv1.domain.repository.DownloadableModelRepository import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.BehaviorSubject diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCaseImplTest.kt new file mode 100644 index 000000000..ff371a2d1 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/downloadable/ScanCustomModelsUseCaseImplTest.kt @@ -0,0 +1,208 @@ +package dev.minios.pdaiv1.domain.usecase.downloadable + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.mockk.every +import io.mockk.mockk +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.File +import kotlin.io.path.createTempDirectory + +class ScanCustomModelsUseCaseImplTest { + + private val stubPreferenceManager = mockk() + private val useCase = ScanCustomModelsUseCaseImpl(stubPreferenceManager) + + private lateinit var tempDir: File + + @Before + fun setUp() { + tempDir = createTempDirectory("scan_custom_models_test").toFile() + } + + @After + fun tearDown() { + tempDir.deleteRecursively() + } + + @Test + fun `given base path does not exist, expected empty list`() { + every { stubPreferenceManager.localOnnxCustomModelPath } returns "/nonexistent/path" + + useCase(LocalAiModel.Type.ONNX) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given base path is not a directory, expected empty list`() { + val file = File(tempDir, "not_a_directory.txt") + file.createNewFile() + + every { stubPreferenceManager.localOnnxCustomModelPath } returns file.absolutePath + + useCase(LocalAiModel.Type.ONNX) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given empty directory, expected empty list`() { + every { stubPreferenceManager.localOnnxCustomModelPath } returns tempDir.absolutePath + + useCase(LocalAiModel.Type.ONNX) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given valid ONNX model directory, expected model in list`() { + val modelDir = File(tempDir, "my_custom_model") + modelDir.mkdirs() + + File(modelDir, "text_encoder").mkdirs() + File(modelDir, "text_encoder/model.ort").createNewFile() + File(modelDir, "unet").mkdirs() + File(modelDir, "unet/model.ort").createNewFile() + File(modelDir, "vae_decoder").mkdirs() + File(modelDir, "vae_decoder/model.ort").createNewFile() + File(modelDir, "tokenizer").mkdirs() + File(modelDir, "tokenizer/vocab.json").createNewFile() + File(modelDir, "tokenizer/merges.txt").createNewFile() + + every { stubPreferenceManager.localOnnxCustomModelPath } returns tempDir.absolutePath + + useCase(LocalAiModel.Type.ONNX) + .test() + .assertNoErrors() + .assertValue { models -> + models.size == 1 && + models[0].id == "CUSTOM_ONNX:my_custom_model" && + models[0].name == "my_custom_model" && + models[0].type == LocalAiModel.Type.ONNX && + models[0].downloaded + } + .await() + .assertComplete() + } + + @Test + fun `given invalid ONNX model directory (missing files), expected empty list`() { + val modelDir = File(tempDir, "incomplete_model") + modelDir.mkdirs() + + File(modelDir, "text_encoder").mkdirs() + File(modelDir, "text_encoder/model.ort").createNewFile() + + every { stubPreferenceManager.localOnnxCustomModelPath } returns tempDir.absolutePath + + useCase(LocalAiModel.Type.ONNX) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given valid QNN model directory with NPU files, expected model in list`() { + val modelDir = File(tempDir, "qnn_model") + modelDir.mkdirs() + + File(modelDir, "clip.bin").createNewFile() + File(modelDir, "unet.bin").createNewFile() + File(modelDir, "vae_decoder.bin").createNewFile() + File(modelDir, "tokenizer.json").createNewFile() + + every { stubPreferenceManager.localQnnCustomModelPath } returns tempDir.absolutePath + + useCase(LocalAiModel.Type.QNN) + .test() + .assertNoErrors() + .assertValue { models -> + models.size == 1 && + models[0].id == "CUSTOM_QNN:qnn_model" && + models[0].type == LocalAiModel.Type.QNN + } + .await() + .assertComplete() + } + + @Test + fun `given valid QNN model directory with CPU files, expected model in list`() { + val modelDir = File(tempDir, "qnn_cpu_model") + modelDir.mkdirs() + + File(modelDir, "clip.mnn").createNewFile() + File(modelDir, "unet.mnn").createNewFile() + File(modelDir, "vae_decoder.mnn").createNewFile() + File(modelDir, "tokenizer.json").createNewFile() + + every { stubPreferenceManager.localQnnCustomModelPath } returns tempDir.absolutePath + + useCase(LocalAiModel.Type.QNN) + .test() + .assertNoErrors() + .assertValue { models -> + models.size == 1 && + models[0].id == "CUSTOM_QNN:qnn_cpu_model" && + models[0].type == LocalAiModel.Type.QNN + } + .await() + .assertComplete() + } + + @Test + fun `given valid MediaPipe model directory, expected model in list`() { + val modelDir = File(tempDir, "mediapipe_model") + modelDir.mkdirs() + + val binsDir = File(modelDir, "bins") + binsDir.mkdirs() + File(binsDir, "model.bin").createNewFile() + + every { stubPreferenceManager.localMediaPipeCustomModelPath } returns tempDir.absolutePath + + useCase(LocalAiModel.Type.MediaPipe) + .test() + .assertNoErrors() + .assertValue { models -> + models.size == 1 && + models[0].id == "CUSTOM_MP:mediapipe_model" && + models[0].type == LocalAiModel.Type.MediaPipe + } + .await() + .assertComplete() + } + + @Test + fun `given MediaPipe directory with QNN-specific files, expected empty list`() { + val modelDir = File(tempDir, "wrong_model") + modelDir.mkdirs() + + val binsDir = File(modelDir, "bins") + binsDir.mkdirs() + File(binsDir, "clip.bin").createNewFile() + File(binsDir, "unet.bin").createNewFile() + + every { stubPreferenceManager.localMediaPipeCustomModelPath } returns tempDir.absolutePath + + useCase(LocalAiModel.Type.MediaPipe) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } +} diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCaseImplTest.kt new file mode 100644 index 000000000..62824362f --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/forgemodule/GetForgeModulesUseCaseImplTest.kt @@ -0,0 +1,57 @@ +package dev.minios.pdaiv1.domain.usecase.forgemodule + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.mocks.mockForgeModules +import dev.minios.pdaiv1.domain.repository.ForgeModulesRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class GetForgeModulesUseCaseImplTest { + + private val stubException = Throwable("Failed to fetch modules.") + private val stubRepository = mock() + + private val useCase = GetForgeModulesUseCaseImpl( + repository = stubRepository, + ) + + @Test + fun `given attempt to get modules, repository returns data, expected valid list value`() { + whenever(stubRepository.fetchModules()) + .thenReturn(Single.just(mockForgeModules)) + + useCase() + .test() + .assertNoErrors() + .assertValue(mockForgeModules) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get modules, repository returns empty list, expected empty list value`() { + whenever(stubRepository.fetchModules()) + .thenReturn(Single.just(emptyList())) + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get modules, repository throws exception, expected error value`() { + whenever(stubRepository.fetchModules()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImplTest.kt similarity index 89% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImplTest.kt index 4f07564e6..cdb35bef8 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteAllGalleryUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery +package dev.minios.pdaiv1.domain.usecase.gallery import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImplTest.kt similarity index 89% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImplTest.kt index f6c678f27..8e11d3dc0 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery +package dev.minios.pdaiv1.domain.usecase.gallery import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImplTest.kt similarity index 90% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImplTest.kt index 9778f650f..26a243ab2 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/DeleteGalleryItemsUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery +package dev.minios.pdaiv1.domain.usecase.gallery import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCaseImplTest.kt index 4ba7d2c42..a518f60b1 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetAllGalleryUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetAllGalleryUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery +package dev.minios.pdaiv1.domain.usecase.gallery import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResults -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResults +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCaseImplTest.kt new file mode 100644 index 000000000..b27ad4d04 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryItemsUseCaseImplTest.kt @@ -0,0 +1,75 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResults +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class GetGalleryItemsUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = GetGalleryItemsUseCaseImpl(stubRepository) + + @Test + fun `given repository returns items, expected valid items list`() { + val ids = listOf(1L, 2L, 3L) + + whenever(stubRepository.getByIds(ids)) + .thenReturn(Single.just(mockAiGenerationResults)) + + useCase(ids) + .test() + .assertNoErrors() + .assertValue(mockAiGenerationResults) + .await() + .assertComplete() + } + + @Test + fun `given repository returns empty list, expected empty list`() { + val ids = listOf(1L, 2L, 3L) + + whenever(stubRepository.getByIds(ids)) + .thenReturn(Single.just(emptyList())) + + useCase(ids) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given empty ids list, expected empty list from repository`() { + val ids = emptyList() + + whenever(stubRepository.getByIds(ids)) + .thenReturn(Single.just(emptyList())) + + useCase(ids) + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given repository throws error, expected error`() { + val ids = listOf(1L, 2L, 3L) + val stubException = Throwable("Database error") + + whenever(stubRepository.getByIds(ids)) + .thenReturn(Single.error(stubException)) + + useCase(ids) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCaseImplTest.kt new file mode 100644 index 000000000..f55a0a61b --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetGalleryPagedIdsUseCaseImplTest.kt @@ -0,0 +1,58 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class GetGalleryPagedIdsUseCaseImplTest { + + private val stubException = Throwable("Failed to get gallery ids.") + private val stubRepository = mock() + + private val useCase = GetGalleryPagedIdsUseCaseImpl( + repository = stubRepository, + ) + + @Test + fun `given attempt to get gallery ids, repository returns data, expected valid list value`() { + val expectedIds = listOf(1L, 2L, 3L, 5598L) + + whenever(stubRepository.getAllIds()) + .thenReturn(Single.just(expectedIds)) + + useCase() + .test() + .assertNoErrors() + .assertValue(expectedIds) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get gallery ids, repository returns empty list, expected empty list value`() { + whenever(stubRepository.getAllIds()) + .thenReturn(Single.just(emptyList())) + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given attempt to get gallery ids, repository throws exception, expected error value`() { + whenever(stubRepository.getAllIds()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImplTest.kt similarity index 85% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImplTest.kt index 18a11c0e9..67141728f 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/GetMediaStoreInfoUseCaseImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery +package dev.minios.pdaiv1.domain.usecase.gallery import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import com.shifthackz.aisdv1.domain.mocks.mockMediaStoreInfo -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.domain.mocks.mockMediaStoreInfo +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt similarity index 89% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt index 33880cd49..ac87cca0f 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.gallery +package dev.minios.pdaiv1.domain.usecase.gallery import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCaseImplTest.kt new file mode 100644 index 000000000..f2a848ca1 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/UnhideItemsUseCaseImplTest.kt @@ -0,0 +1,40 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Completable +import org.junit.Test + +class UnhideItemsUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = UnhideItemsUseCaseImpl(stubRepository) + + @Test + fun `given repository unhid items successfully, expected complete`() { + whenever(stubRepository.unhideByIds(listOf(5598L, 151297L))) + .thenReturn(Completable.complete()) + + useCase(listOf(5598L, 151297L)) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given repository unhid items with fail, expected error`() { + val stubException = Throwable("Database communication error.") + + whenever(stubRepository.unhideByIds(listOf(5598L, 151297L))) + .thenReturn(Completable.error(stubException)) + + useCase(listOf(5598L, 151297L)) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCaseImplTest.kt new file mode 100644 index 000000000..a51f18806 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/gallery/UnlikeItemsUseCaseImplTest.kt @@ -0,0 +1,40 @@ +package dev.minios.pdaiv1.domain.usecase.gallery + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Completable +import org.junit.Test + +class UnlikeItemsUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = UnlikeItemsUseCaseImpl(stubRepository) + + @Test + fun `given repository unliked items successfully, expected complete`() { + whenever(stubRepository.unlikeByIds(listOf(5598L, 151297L))) + .thenReturn(Completable.complete()) + + useCase(listOf(5598L, 151297L)) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given repository unliked items with fail, expected error`() { + val stubException = Throwable("Database communication error.") + + whenever(stubRepository.unlikeByIds(listOf(5598L, 151297L))) + .thenReturn(Completable.error(stubException)) + + useCase(listOf(5598L, 151297L)) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCaseImplTest.kt new file mode 100644 index 000000000..454909699 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/FalAiGenerationUseCaseImplTest.kt @@ -0,0 +1,113 @@ +package dev.minios.pdaiv1.domain.usecase.generation + +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.domain.mocks.mockFalAiEndpoint +import dev.minios.pdaiv1.domain.mocks.mockFalAiPayload +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class FalAiGenerationUseCaseImplTest { + + private val stubException = Throwable("Something went wrong.") + private val stubFalAiEndpointRepository = mock() + private val stubFalAiGenerationRepository = mock() + private val stubSaveGenerationResultUseCase = mock() + + private val useCase = FalAiGenerationUseCaseImpl( + falAiEndpointRepository = stubFalAiEndpointRepository, + falAiGenerationRepository = stubFalAiGenerationRepository, + saveGenerationResultUseCase = stubSaveGenerationResultUseCase, + ) + + @Test + fun `given valid payload and endpoint found, generation succeeds, expected valid results list`() { + val expectedResults = listOf(mockAiGenerationResult) + + whenever(stubFalAiEndpointRepository.getAll()) + .thenReturn(Single.just(listOf(mockFalAiEndpoint))) + + whenever(stubFalAiGenerationRepository.generateDynamic(any(), any())) + .thenReturn(Single.just(expectedResults)) + + whenever(stubSaveGenerationResultUseCase.invoke(any())) + .thenReturn(Completable.complete()) + + useCase(mockFalAiPayload) + .test() + .assertNoErrors() + .assertValue(expectedResults) + .await() + .assertComplete() + } + + @Test + fun `given valid payload but endpoint not found, expected error value`() { + val payloadWithWrongEndpoint = mockFalAiPayload.copy(endpointId = "non-existent") + + whenever(stubFalAiEndpointRepository.getAll()) + .thenReturn(Single.just(listOf(mockFalAiEndpoint))) + + useCase(payloadWithWrongEndpoint) + .test() + .assertError { it.message?.contains("Endpoint not found") == true } + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given valid payload, endpoint repository fails, expected error value`() { + whenever(stubFalAiEndpointRepository.getAll()) + .thenReturn(Single.error(stubException)) + + useCase(mockFalAiPayload) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given valid payload and endpoint found, generation fails, expected error value`() { + whenever(stubFalAiEndpointRepository.getAll()) + .thenReturn(Single.just(listOf(mockFalAiEndpoint))) + + whenever(stubFalAiGenerationRepository.generateDynamic(any(), any())) + .thenReturn(Single.error(stubException)) + + useCase(mockFalAiPayload) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } + + @Test + fun `given valid payload and endpoint found, generation succeeds but save fails, expected error value`() { + val expectedResults = listOf(mockAiGenerationResult) + + whenever(stubFalAiEndpointRepository.getAll()) + .thenReturn(Single.just(listOf(mockFalAiEndpoint))) + + whenever(stubFalAiGenerationRepository.generateDynamic(any(), any())) + .thenReturn(Single.just(expectedResults)) + + whenever(stubSaveGenerationResultUseCase.invoke(any())) + .thenReturn(Completable.error(stubException)) + + useCase(mockFalAiPayload) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImplTest.kt index 5ca053dad..d7af0b621 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultPagedUseCaseImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResults -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResults +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCaseImplTest.kt similarity index 86% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCaseImplTest.kt index 07a2f519e..4ae2a790d 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetGenerationResultUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetGenerationResultUseCaseImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCaseImplTest.kt similarity index 90% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCaseImplTest.kt index 8cbe8a820..760b677ac 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/GetRandomImageUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/GetRandomImageUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import android.graphics.Bitmap import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.RandomImageRepository +import dev.minios.pdaiv1.domain.repository.RandomImageRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCaseImplTest.kt similarity index 93% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCaseImplTest.kt index 5641d4f2d..0bda15ac0 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ImageToImageUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ImageToImageUseCaseImplTest.kt @@ -1,17 +1,18 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.domain.mocks.mockImageToImagePayload -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.SwarmUiGenerationRepository +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.domain.mocks.mockImageToImagePayload +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository import io.reactivex.rxjava3.core.Single import org.junit.Test @@ -23,6 +24,7 @@ class ImageToImageUseCaseImplTest { private val stubHordeGenerationRepository = mock() private val stubHuggingFaceGenerationRepository = mock() private val stubStabilityAiGenerationRepository = mock() + private val stubQnnGenerationRepository = mock() private val stubPreferenceManager = mock() private val useCase = ImageToImageUseCaseImpl( @@ -31,6 +33,7 @@ class ImageToImageUseCaseImplTest { hordeGenerationRepository = stubHordeGenerationRepository, huggingFaceGenerationRepository = stubHuggingFaceGenerationRepository, stabilityAiGenerationRepository = stubStabilityAiGenerationRepository, + qnnGenerationRepository = stubQnnGenerationRepository, preferenceManager = stubPreferenceManager, ) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCaseImplTest.kt index 895bb8a86..ea3439648 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/InterruptGenerationUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/InterruptGenerationUseCaseImplTest.kt @@ -1,12 +1,13 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository import io.reactivex.rxjava3.core.Completable import org.junit.Test @@ -16,12 +17,14 @@ class InterruptGenerationUseCaseImplTest { private val stubStableDiffusionGenerationRepository = mock() private val stubHordeGenerationRepository = mock() private val stubLocalDiffusionGenerationRepository = mock() + private val stubQnnGenerationRepository = mock() private val stubPreferenceManager = mock() private val useCase = InterruptGenerationUseCaseImpl( stableDiffusionGenerationRepository = stubStableDiffusionGenerationRepository, hordeGenerationRepository = stubHordeGenerationRepository, localDiffusionGenerationRepository = stubLocalDiffusionGenerationRepository, + qnnGenerationRepository = stubQnnGenerationRepository, preferenceManager = stubPreferenceManager, ) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImplTest.kt index 094b08793..985f61026 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveHordeProcessStatusUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.BehaviorSubject diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImplTest.kt similarity index 81% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImplTest.kt index 47836c4ec..dca5cdfbb 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/ObserveLocalDiffusionProcessStatusUseCaseImplTest.kt @@ -1,9 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.BehaviorSubject import org.junit.Before @@ -13,14 +14,21 @@ class ObserveLocalDiffusionProcessStatusUseCaseImplTest { private val stubException = Throwable("Error loading Local Diffusion.") private val stubLocalStatus = BehaviorSubject.create() + private val stubQnnStatus = BehaviorSubject.create() private val stubRepository = mock() + private val stubQnnRepository = mock() - private val useCase = ObserveLocalDiffusionProcessStatusUseCaseImpl(stubRepository) + private val useCase = ObserveLocalDiffusionProcessStatusUseCaseImpl( + stubRepository, + stubQnnRepository + ) @Before fun initialize() { whenever(stubRepository.observeStatus()) .thenReturn(stubLocalStatus) + whenever(stubQnnRepository.observeStatus()) + .thenReturn(stubQnnStatus) } @Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCaseImplTest.kt similarity index 85% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCaseImplTest.kt index 750dc81b4..7b88ac24b 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/SaveGenerationResultUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/SaveGenerationResultUseCaseImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.domain.repository.GenerationResultRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCaseImplTest.kt similarity index 81% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCaseImplTest.kt index 98505eac2..88ce2cb36 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/generation/TextToImageUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/generation/TextToImageUseCaseImplTest.kt @@ -1,20 +1,22 @@ -package com.shifthackz.aisdv1.domain.usecase.generation +package dev.minios.pdaiv1.domain.usecase.generation import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.domain.mocks.mockTextToImagePayload -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.HordeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.HuggingFaceGenerationRepository -import com.shifthackz.aisdv1.domain.repository.LocalDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.MediaPipeGenerationRepository -import com.shifthackz.aisdv1.domain.repository.OpenAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StabilityAiGenerationRepository -import com.shifthackz.aisdv1.domain.repository.StableDiffusionGenerationRepository -import com.shifthackz.aisdv1.domain.repository.SwarmUiGenerationRepository +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.domain.mocks.mockTextToImagePayload +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.HordeGenerationRepository +import dev.minios.pdaiv1.domain.repository.HuggingFaceGenerationRepository +import dev.minios.pdaiv1.domain.repository.LocalDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.MediaPipeGenerationRepository +import dev.minios.pdaiv1.domain.repository.OpenAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.StabilityAiGenerationRepository +import dev.minios.pdaiv1.domain.repository.QnnGenerationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionGenerationRepository +import dev.minios.pdaiv1.domain.repository.SwarmUiGenerationRepository import io.reactivex.rxjava3.core.Single import org.junit.Test @@ -26,9 +28,11 @@ class TextToImageUseCaseImplTest { private val stubHuggingFaceGenerationRepository = mock() private val stubOpenAiGenerationRepository = mock() private val stubStabilityAiGenerationRepository = mock() + private val stubFalAiGenerationRepository = mock() private val stubSwarmUiGenerationRepository = mock() private val stubLocalDiffusionGenerationRepository = mock() private val stubMediaPipeGenerationRepository = mock() + private val stubQnnGenerationRepository = mock() private val stubPreferenceManager = mock() private val useCase = TextToImageUseCaseImpl( @@ -37,9 +41,11 @@ class TextToImageUseCaseImplTest { huggingFaceGenerationRepository = stubHuggingFaceGenerationRepository, openAiGenerationRepository = stubOpenAiGenerationRepository, stabilityAiGenerationRepository = stubStabilityAiGenerationRepository, - localDiffusionGenerationRepository = stubLocalDiffusionGenerationRepository, + falAiGenerationRepository = stubFalAiGenerationRepository, swarmUiGenerationRepository = stubSwarmUiGenerationRepository, + localDiffusionGenerationRepository = stubLocalDiffusionGenerationRepository, mediaPipeGenerationRepository = stubMediaPipeGenerationRepository, + qnnGenerationRepository = stubQnnGenerationRepository, preferenceManager = stubPreferenceManager, ) @@ -426,4 +432,68 @@ class TextToImageUseCaseImplTest { .await() .assertNotComplete() } + + @Test + fun `given source is FAL_AI, batch count is 1, generated successfully, expected generations list with size 1`() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.FAL_AI) + + whenever(stubFalAiGenerationRepository.generateFromText(any())) + .thenReturn(Single.just(mockAiGenerationResult)) + + val stubBatchCount = 1 + val stubPayload = mockTextToImagePayload.copy(batchCount = stubBatchCount) + + val expectedResult = listOf(mockAiGenerationResult) + + useCase(stubPayload) + .test() + .assertNoErrors() + .assertValue { generations -> + generations.size == stubBatchCount && expectedResult == generations + } + .await() + .assertComplete() + } + + @Test + fun `given source is FAL_AI, batch count is 10, generated successfully, expected generations list with size 10`() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.FAL_AI) + + whenever(stubFalAiGenerationRepository.generateFromText(any())) + .thenReturn(Single.just(mockAiGenerationResult)) + + val stubBatchCount = 10 + val stubPayload = mockTextToImagePayload.copy(batchCount = stubBatchCount) + + val expectedResult = (0 until 10).map { mockAiGenerationResult } + + useCase(stubPayload) + .test() + .assertNoErrors() + .assertValue { generations -> + generations.size == stubBatchCount && expectedResult == generations + } + .await() + .assertComplete() + } + + @Test + fun `given source is FAL_AI, batch count is 1, generate failed, expected error`() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.FAL_AI) + + whenever(stubFalAiGenerationRepository.generateFromText(any())) + .thenReturn(Single.error(stubException)) + + val stubBatchCount = 1 + val stubPayload = mockTextToImagePayload.copy(batchCount = stubBatchCount) + + useCase(stubPayload) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } } diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImplTest.kt new file mode 100644 index 000000000..d5ecb7323 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/huggingface/FetchAndGetHuggingFaceModelsUseCaseImplTest.kt @@ -0,0 +1,68 @@ +package dev.minios.pdaiv1.domain.usecase.huggingface + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.mocks.mockHuggingFaceModels +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.HuggingFaceModelsRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Before +import org.junit.Test + +class FetchAndGetHuggingFaceModelsUseCaseImplTest { + + private val stubPreferenceManager = mock() + private val stubRepository = mock() + + private val useCase = FetchAndGetHuggingFaceModelsUseCaseImpl( + preferenceManager = stubPreferenceManager, + huggingFaceModelsRepository = stubRepository, + ) + + @Before + fun initialize() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.HUGGING_FACE) + } + + @Test + fun `given repository provided models list, expected valid list value`() { + whenever(stubRepository.fetchAndGetHuggingFaceModels()) + .thenReturn(Single.just(mockHuggingFaceModels)) + + useCase() + .test() + .assertNoErrors() + .assertValue(mockHuggingFaceModels) + .await() + .assertComplete() + } + + @Test + fun `given repository provided empty models list, expected empty list value`() { + whenever(stubRepository.fetchAndGetHuggingFaceModels()) + .thenReturn(Single.just(emptyList())) + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given repository thrown exception, expected error value`() { + val stubException = Throwable("Unknown error occurred.") + + whenever(stubRepository.fetchAndGetHuggingFaceModels()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCaseImplTest.kt new file mode 100644 index 000000000..e1e0fe448 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/report/SendReportUseCaseImplTest.kt @@ -0,0 +1,48 @@ +package dev.minios.pdaiv1.domain.usecase.report + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.domain.repository.ReportRepository +import io.reactivex.rxjava3.core.Completable +import org.junit.Test + +class SendReportUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = SendReportUseCaseImpl(stubRepository) + + @Test + fun `given repository send successful, expected complete`() { + val text = "Test report text" + val reason = ReportReason.Other + val image = "base64image" + + whenever(stubRepository.send(text, reason, image)) + .thenReturn(Completable.complete()) + + useCase(text, reason, image) + .test() + .assertNoErrors() + .await() + .assertComplete() + } + + @Test + fun `given repository send failed, expected error`() { + val text = "Test report text" + val reason = ReportReason.InappropriateContent + val image = "" + val stubException = Throwable("Network error") + + whenever(stubRepository.send(text, reason, image)) + .thenReturn(Completable.error(stubException)) + + useCase(text, reason, image) + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImplTest.kt similarity index 89% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImplTest.kt index bc806e019..170720456 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdembedding/FetchAndGetEmbeddingsUseCaseImplTest.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.domain.usecase.sdembedding +package dev.minios.pdaiv1.domain.usecase.sdembedding import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockEmbeddings -import com.shifthackz.aisdv1.domain.repository.EmbeddingsRepository +import dev.minios.pdaiv1.domain.mocks.mockEmbeddings +import dev.minios.pdaiv1.domain.repository.EmbeddingsRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImplTest.kt similarity index 87% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImplTest.kt index a8122d413..9e410c0f8 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdhypernet/FetchAndGetHyperNetworksUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.sdhypernet +package dev.minios.pdaiv1.domain.usecase.sdhypernet import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockStableDiffusionHyperNetworks -import com.shifthackz.aisdv1.domain.repository.StableDiffusionHyperNetworksRepository +import dev.minios.pdaiv1.domain.mocks.mockStableDiffusionHyperNetworks +import dev.minios.pdaiv1.domain.repository.StableDiffusionHyperNetworksRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImplTest.kt similarity index 89% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImplTest.kt index 643bf9f20..dfa6dcd45 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdlora/FetchAndGetLorasUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.sdlora +package dev.minios.pdaiv1.domain.usecase.sdlora import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockLoRAs -import com.shifthackz.aisdv1.domain.repository.LorasRepository +import dev.minios.pdaiv1.domain.mocks.mockLoRAs +import dev.minios.pdaiv1.domain.repository.LorasRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImplTest.kt new file mode 100644 index 000000000..83db3406e --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdmodel/GetStableDiffusionModelsUseCaseImplTest.kt @@ -0,0 +1,132 @@ +package dev.minios.pdaiv1.domain.usecase.sdmodel + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.entity.ServerConfiguration +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.mocks.mockServerConfiguration +import dev.minios.pdaiv1.domain.mocks.mockStableDiffusionModels +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository +import dev.minios.pdaiv1.domain.repository.StableDiffusionModelsRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class GetStableDiffusionModelsUseCaseImplTest { + + private val stubPreferenceManager = mock() + private val stubServerConfigurationRepository = mock() + private val stubSdModelsRepository = mock() + + private val useCase = GetStableDiffusionModelsUseCaseImpl( + preferenceManager = stubPreferenceManager, + serverConfigurationRepository = stubServerConfigurationRepository, + sdModelsRepository = stubSdModelsRepository, + ) + + @Before + fun initialize() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.AUTOMATIC1111) + } + + @Test + fun `given repository returns list with value present in configuration, expected list with selected value`() { + whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) + .thenReturn(Single.just(mockServerConfiguration)) + + whenever(stubSdModelsRepository.fetchAndGetModels()) + .thenReturn(Single.just(mockStableDiffusionModels)) + + val expectedValue = mockStableDiffusionModels.map { + it to (it.title == mockServerConfiguration.sdModelCheckpoint) + } + + useCase() + .test() + .assertNoErrors() + .assertValue(expectedValue) + .also { + Assert.assertEquals( + true, + expectedValue.any { (_, selected) -> selected }, + ) + } + .await() + .assertComplete() + } + + @Test + fun `given repository returns list with no value present in configuration, expected list without selected value`() { + val stubServerConfiguration = ServerConfiguration("nonsense") + + whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) + .thenReturn(Single.just(stubServerConfiguration)) + + whenever(stubSdModelsRepository.fetchAndGetModels()) + .thenReturn(Single.just(mockStableDiffusionModels)) + + val expectedValue = mockStableDiffusionModels.map { + it to (it.title == stubServerConfiguration.sdModelCheckpoint) + } + + useCase() + .test() + .assertNoErrors() + .assertValue(expectedValue) + .also { + Assert.assertEquals( + true, + !expectedValue.any { (_, selected) -> selected }, + ) + } + .await() + .assertComplete() + } + + @Test + fun `given exception while fetching configuration, expected error value`() { + val stubException = Throwable("Network error.") + + whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given exception while fetching models, expected error value`() { + val stubException = Throwable("Network error.") + + whenever(stubServerConfigurationRepository.fetchAndGetConfiguration()) + .thenReturn(Single.just(mockServerConfiguration)) + + whenever(stubSdModelsRepository.fetchAndGetModels()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } + + @Test + fun `given source is not AUTOMATIC1111, expected empty list without network requests`() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.FAL_AI) + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImplTest.kt similarity index 92% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImplTest.kt index 8ac4b20b1..690eaa74a 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdmodel/SelectStableDiffusionModelUseCaseImplTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.domain.usecase.sdmodel +package dev.minios.pdaiv1.domain.usecase.sdmodel import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockServerConfiguration -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.ServerConfigurationRepository +import dev.minios.pdaiv1.domain.mocks.mockServerConfiguration +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.ServerConfigurationRepository import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImplTest.kt similarity index 88% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImplTest.kt index 5da15b29e..951d7f2f7 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/sdsampler/GetStableDiffusionSamplersUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.sdsampler +package dev.minios.pdaiv1.domain.usecase.sdsampler import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.mocks.mockStableDiffusionSamplers -import com.shifthackz.aisdv1.domain.repository.StableDiffusionSamplersRepository +import dev.minios.pdaiv1.domain.mocks.mockStableDiffusionSamplers +import dev.minios.pdaiv1.domain.repository.StableDiffusionSamplersRepository import io.reactivex.rxjava3.core.Single import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCaseImplTest.kt similarity index 87% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCaseImplTest.kt index f0ee77995..eec7c4cb9 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToA1111UseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToA1111UseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration -import com.shifthackz.aisdv1.domain.usecase.caching.DataPreLoaderUseCase -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestConnectivityUseCase +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.usecase.caching.DataPreLoaderUseCase +import dev.minios.pdaiv1.domain.usecase.connectivity.TestConnectivityUseCase import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCaseImplTest.kt new file mode 100644 index 000000000..af49c8f35 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToFalAiUseCaseImplTest.kt @@ -0,0 +1,180 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.connectivity.TestFalAiApiKeyUseCase +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.plugins.RxJavaPlugins +import io.reactivex.rxjava3.schedulers.TestScheduler +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.util.concurrent.TimeUnit + +class ConnectToFalAiUseCaseImplTest { + + private val stubThrowable = Throwable("Something went wrong.") + private val stubGetConfigurationUseCase = mockk() + private val stubSetServerConfigurationUseCase = mockk() + private val stubTestFalAiApiKeyUseCase = mockk() + private val stubPreferenceManager = mockk(relaxed = true) + + private val testScheduler = TestScheduler() + + private val useCase = ConnectToFalAiUseCaseImpl( + getConfigurationUseCase = stubGetConfigurationUseCase, + setServerConfigurationUseCase = stubSetServerConfigurationUseCase, + testFalAiApiKeyUseCase = stubTestFalAiApiKeyUseCase, + preferenceManager = stubPreferenceManager, + ) + + @Before + fun setUp() { + RxJavaPlugins.setComputationSchedulerHandler { testScheduler } + } + + @After + fun tearDown() { + RxJavaPlugins.reset() + } + + @Test + fun `given connection process successful, API key is valid, expected success result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestFalAiApiKeyUseCase() + } returns Single.just(true) + + val testObserver = useCase("test-api-key", "fal-ai/flux/schnell") + .test() + + testScheduler.advanceTimeBy(3, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .await() + .assertValue(Result.success(Unit)) + .assertComplete() + + verify { stubPreferenceManager.falAiSelectedEndpointId = "fal-ai/flux/schnell" } + } + + @Test + fun `given connection process successful, API key is NOT valid, expected failure result value with rollback`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestFalAiApiKeyUseCase() + } returns Single.just(false) + + val testObserver = useCase("bad-api-key", "fal-ai/flux/schnell") + .test() + + testScheduler.advanceTimeBy(3, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .await() + .assertValue { actual -> + actual.isFailure + && actual.exceptionOrNull() is IllegalStateException + && actual.exceptionOrNull()?.message == "Bad key" + } + .assertComplete() + } + + @Test + fun `given get configuration fails, expected failure result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.error(stubThrowable) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestFalAiApiKeyUseCase() + } returns Single.just(true) + + val testObserver = useCase("test-api-key", "fal-ai/flux/schnell") + .test() + + testScheduler.advanceTimeBy(3, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .await() + .assertValue(Result.failure(stubThrowable)) + .assertComplete() + } + + @Test + fun `given set configuration fails, expected failure result value with rollback`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.error(stubThrowable) andThen Completable.complete() + + every { + stubTestFalAiApiKeyUseCase() + } returns Single.just(true) + + val testObserver = useCase("test-api-key", "fal-ai/flux/schnell") + .test() + + testScheduler.advanceTimeBy(3, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .await() + .assertValue(Result.failure(stubThrowable)) + .assertComplete() + } + + @Test + fun `given API key test fails with exception, expected failure result value with rollback`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestFalAiApiKeyUseCase() + } returns Single.error(stubThrowable) + + val testObserver = useCase("test-api-key", "fal-ai/flux/schnell") + .test() + + testScheduler.advanceTimeBy(3, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .await() + .assertValue(Result.failure(stubThrowable)) + .assertComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCaseImplTest.kt similarity index 93% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCaseImplTest.kt index aa12885ad..4020117ea 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHordeUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHordeUseCaseImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHordeApiKeyUseCase +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHordeApiKeyUseCase import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImplTest.kt similarity index 93% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImplTest.kt index c66c336da..2020c6b5e 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToHuggingFaceUseCaseImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCase +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.usecase.connectivity.TestHuggingFaceApiKeyUseCase import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImplTest.kt similarity index 93% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImplTest.kt index 33a09c57d..a0ee9692b 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToLocalDiffusionUseCaseImplTest.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.mocks.mockConfiguration import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImplTest.kt new file mode 100644 index 000000000..0485b7f81 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToMediaPipeUseCaseImplTest.kt @@ -0,0 +1,70 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class ConnectToMediaPipeUseCaseImplTest { + + private val stubThrowable = Throwable("Something went wrong.") + private val stubGetConfigurationUseCase = mockk() + private val stubSetServerConfigurationUseCase = mockk() + + private val useCase = ConnectToMediaPipeUseCaseImpl( + getConfigurationUseCase = stubGetConfigurationUseCase, + setServerConfigurationUseCase = stubSetServerConfigurationUseCase, + ) + + @Test + fun `given connection process successful, expected success result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + useCase("mediapipe-model-id") + .test() + .assertNoErrors() + .assertValue(Result.success(Unit)) + .await() + .assertComplete() + } + + @Test + fun `given connection process failed on get configuration, expected error result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.error(stubThrowable) + + useCase("mediapipe-model-id") + .test() + .assertNoErrors() + .assertValue(Result.failure(stubThrowable)) + .await() + .assertComplete() + } + + @Test + fun `given connection process failed on set configuration, expected error result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.error(stubThrowable) + + useCase("mediapipe-model-id") + .test() + .assertNoErrors() + .assertValue(Result.failure(stubThrowable)) + .await() + .assertComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCaseImplTest.kt similarity index 93% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCaseImplTest.kt index 470b26571..8cb6ae679 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToOpenAiUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToOpenAiUseCaseImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCase +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.usecase.connectivity.TestOpenAiApiKeyUseCase import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCaseImplTest.kt new file mode 100644 index 000000000..3d8699a19 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToQnnUseCaseImplTest.kt @@ -0,0 +1,96 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class ConnectToQnnUseCaseImplTest { + + private val stubThrowable = Throwable("Something went wrong.") + private val stubGetConfigurationUseCase = mockk() + private val stubSetServerConfigurationUseCase = mockk() + private val stubPreferenceManager = mockk(relaxed = true) + + private val useCase = ConnectToQnnUseCaseImpl( + getConfigurationUseCase = stubGetConfigurationUseCase, + setServerConfigurationUseCase = stubSetServerConfigurationUseCase, + preferenceManager = stubPreferenceManager, + ) + + @Test + fun `given connection process successful with CPU mode, expected success result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + useCase("qnn-model-id", runOnCpu = true) + .test() + .assertNoErrors() + .assertValue(Result.success(Unit)) + .await() + .assertComplete() + + verify { stubPreferenceManager.localQnnRunOnCpu = true } + } + + @Test + fun `given connection process successful with NPU mode, expected success result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + useCase("qnn-model-id", runOnCpu = false) + .test() + .assertNoErrors() + .assertValue(Result.success(Unit)) + .await() + .assertComplete() + + verify { stubPreferenceManager.localQnnRunOnCpu = false } + } + + @Test + fun `given connection process failed on get configuration, expected error result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.error(stubThrowable) + + useCase("qnn-model-id", runOnCpu = true) + .test() + .assertNoErrors() + .assertValue(Result.failure(stubThrowable)) + .await() + .assertComplete() + } + + @Test + fun `given connection process failed on set configuration, expected error result value`() { + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.error(stubThrowable) + + useCase("qnn-model-id", runOnCpu = true) + .test() + .assertNoErrors() + .assertValue(Result.failure(stubThrowable)) + .await() + .assertComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImplTest.kt similarity index 93% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImplTest.kt index a3d2a4e73..90c2d4171 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToStabilityAiUseCaseImplTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration -import com.shifthackz.aisdv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCase +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.usecase.connectivity.TestStabilityAiApiKeyUseCase import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Completable diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImplTest.kt new file mode 100644 index 000000000..a95e83787 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/ConnectToSwarmUiUseCaseImplTest.kt @@ -0,0 +1,149 @@ +package dev.minios.pdaiv1.domain.usecase.settings + +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.usecase.connectivity.TestSwarmUiConnectivityUseCase +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.plugins.RxJavaPlugins +import io.reactivex.rxjava3.schedulers.TestScheduler +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +class ConnectToSwarmUiUseCaseImplTest { + + private val testScheduler = TestScheduler() + private val stubThrowable = Throwable("Something went wrong.") + private val stubGetConfigurationUseCase = mockk() + private val stubSetServerConfigurationUseCase = mockk() + private val stubTestSwarmUiConnectivityUseCase = mockk() + + private val useCase = ConnectToSwarmUiUseCaseImpl( + getConfigurationUseCase = stubGetConfigurationUseCase, + setServerConfigurationUseCase = stubSetServerConfigurationUseCase, + testSwarmUiConnectivityUseCase = stubTestSwarmUiConnectivityUseCase, + ) + + @Before + fun setUp() { + RxJavaPlugins.setComputationSchedulerHandler { testScheduler } + } + + @After + fun tearDown() { + RxJavaPlugins.reset() + } + + @Test + fun `given connection process successful, expected success result value`() { + val url = "http://localhost:7801" + val credentials = AuthorizationCredentials.None + + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestSwarmUiConnectivityUseCase(url) + } returns Completable.complete() + + val testObserver = useCase(url, credentials).test() + + testScheduler.advanceTimeBy(3, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .assertValue(Result.success(Unit)) + .assertComplete() + } + + @Test + fun `given connection process failed on get configuration, expected error result value`() { + val url = "http://localhost:7801" + val credentials = AuthorizationCredentials.None + + every { + stubGetConfigurationUseCase() + } returns Single.error(stubThrowable) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestSwarmUiConnectivityUseCase(url) + } returns Completable.complete() + + val testObserver = useCase(url, credentials).test() + + testScheduler.advanceTimeBy(30, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .assertValue { it.isFailure && it.exceptionOrNull() == stubThrowable } + .assertComplete() + } + + @Test + fun `given connectivity test failed, expected error result value and configuration rollback`() { + val url = "http://localhost:7801" + val credentials = AuthorizationCredentials.None + + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestSwarmUiConnectivityUseCase(url) + } returns Completable.error(stubThrowable) + + val testObserver = useCase(url, credentials).test() + + testScheduler.advanceTimeBy(3, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .assertValue { it.isFailure } + .assertComplete() + } + + @Test + fun `given connection process times out, expected timeout error result value`() { + val url = "http://localhost:7801" + val credentials = AuthorizationCredentials.None + + every { + stubGetConfigurationUseCase() + } returns Single.just(mockConfiguration) + + every { + stubSetServerConfigurationUseCase(any()) + } returns Completable.complete() + + every { + stubTestSwarmUiConnectivityUseCase(url) + } returns Completable.never() + + val testObserver = useCase(url, credentials).test() + + testScheduler.advanceTimeBy(31, TimeUnit.SECONDS) + + testObserver + .assertNoErrors() + .assertValue { it.isFailure && it.exceptionOrNull() is TimeoutException } + .assertComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCaseImplTest.kt similarity index 80% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCaseImplTest.kt index 5a5d12bcb..8082dee7f 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/GetConfigurationUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/GetConfigurationUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationStore -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationStore +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import org.junit.Test @@ -84,6 +84,18 @@ class GetConfigurationUseCaseImplTest { stubPreferenceManager::localMediaPipeCustomModelPath.get() } returns mockConfiguration.localMediaPipeModelPath + every { + stubPreferenceManager::falAiApiKey.get() + } returns mockConfiguration.falAiApiKey + + every { + stubPreferenceManager::localQnnModelId.get() + } returns mockConfiguration.localQnnModelId + + every { + stubPreferenceManager::localQnnCustomModelPath.get() + } returns mockConfiguration.localQnnModelPath + useCase .invoke() .test() diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCaseImplTest.kt similarity index 81% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCaseImplTest.kt index a16201204..28dc5e96b 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/settings/SetServerConfigurationUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/settings/SetServerConfigurationUseCaseImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.domain.usecase.settings +package dev.minios.pdaiv1.domain.usecase.settings -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationStore -import com.shifthackz.aisdv1.domain.mocks.mockConfiguration -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationStore +import dev.minios.pdaiv1.domain.mocks.mockConfiguration +import dev.minios.pdaiv1.domain.preference.PreferenceManager import io.mockk.every import io.mockk.mockk import org.junit.Test @@ -83,6 +83,18 @@ class SetServerConfigurationUseCaseImplTest { stubPreferenceManager::localMediaPipeCustomModelPath.set(any()) } returns Unit + every { + stubPreferenceManager::falAiApiKey.set(any()) + } returns Unit + + every { + stubPreferenceManager::localQnnModelId.set(any()) + } returns Unit + + every { + stubPreferenceManager::localQnnCustomModelPath.set(any()) + } returns Unit + useCase .invoke(mockConfiguration) .test() diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCaseImplTest.kt similarity index 95% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCaseImplTest.kt index 092b4c19b..064538ed7 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/splash/SplashNavigationUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/splash/SplashNavigationUseCaseImplTest.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.domain.usecase.splash +package dev.minios.pdaiv1.domain.usecase.splash import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager import org.junit.Test class SplashNavigationUseCaseImplTest { diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImplTest.kt new file mode 100644 index 000000000..5d6c49474 --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/stabilityai/FetchAndGetStabilityAiEnginesUseCaseImplTest.kt @@ -0,0 +1,57 @@ +package dev.minios.pdaiv1.domain.usecase.stabilityai + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.mocks.mockStabilityAiEngines +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.StabilityAiEnginesRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Before +import org.junit.Test + +class FetchAndGetStabilityAiEnginesUseCaseImplTest { + + private val stubRepository = mock() + + private val stubPreferenceManager = mock() + + private val useCase = FetchAndGetStabilityAiEnginesUseCaseImpl( + repository = stubRepository, + preferenceManager = stubPreferenceManager, + ) + + @Before + fun initialize() { + whenever(stubPreferenceManager.source) + .thenReturn(ServerSource.STABILITY_AI) + } + + @Test + fun `given repository returned engines list, id present in preference, expected the same engines list, id not changed`() { + whenever(stubRepository.fetchAndGet()) + .thenReturn(Single.just(mockStabilityAiEngines)) + + whenever(stubPreferenceManager::stabilityAiEngineId.get()) + .thenReturn("engine_1") + + useCase() + .test() + .assertNoErrors() + .assertValue(mockStabilityAiEngines) + .assertComplete() + } + + @Test + fun `given repository thrown exception, expected the same exception`() { + val stubException = Throwable("Network exception") + + whenever(stubRepository.fetchAndGet()) + .thenReturn(Single.error(stubException)) + + useCase() + .test() + .assertError(stubException) + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImplTest.kt index a1b189bcc..2c5226556 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/stabilityai/ObserveStabilityAiCreditsUseCaseImplTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.domain.usecase.stabilityai +package dev.minios.pdaiv1.domain.usecase.stabilityai import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.mocks.mockSettings -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.repository.StabilityAiCreditsRepository +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.mocks.mockSettings +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.StabilityAiCreditsRepository import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.BehaviorSubject diff --git a/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImplTest.kt new file mode 100644 index 000000000..24f1f788a --- /dev/null +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/swarmmodel/FetchAndGetSwarmUiModelsUseCaseImplTest.kt @@ -0,0 +1,94 @@ +package dev.minios.pdaiv1.domain.usecase.swarmmodel + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.mocks.mockSwarmUiModels +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.SwarmUiModelsRepository +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class FetchAndGetSwarmUiModelsUseCaseImplTest { + + private val stubPreferenceManager = mockk(relaxed = true) + private val stubRepository = mockk() + + private val useCase = FetchAndGetSwarmUiModelsUseCaseImpl( + preferenceManager = stubPreferenceManager, + repository = stubRepository, + ) + + @Test + fun `given source is not SWARM_UI, expected empty list`() { + every { stubPreferenceManager.source } returns ServerSource.AUTOMATIC1111 + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + } + + @Test + fun `given source is SWARM_UI and repository returns models, expected models list`() { + every { stubPreferenceManager.source } returns ServerSource.SWARM_UI + every { stubPreferenceManager.swarmUiModel } returns "mock-model" + every { stubRepository.fetchAndGetModels() } returns Single.just(mockSwarmUiModels) + + useCase() + .test() + .assertNoErrors() + .assertValue(mockSwarmUiModels) + .await() + .assertComplete() + } + + @Test + fun `given source is SWARM_UI and current model not in list, expected model updated to first`() { + every { stubPreferenceManager.source } returns ServerSource.SWARM_UI + every { stubPreferenceManager.swarmUiModel } returns "nonexistent-model" + every { stubRepository.fetchAndGetModels() } returns Single.just(mockSwarmUiModels) + + useCase() + .test() + .assertNoErrors() + .assertValue(mockSwarmUiModels) + .await() + .assertComplete() + + verify { stubPreferenceManager.swarmUiModel = "mock-model" } + } + + @Test + fun `given source is SWARM_UI and repository returns empty list, expected empty list`() { + every { stubPreferenceManager.source } returns ServerSource.SWARM_UI + every { stubPreferenceManager.swarmUiModel } returns "mock-model" + every { stubRepository.fetchAndGetModels() } returns Single.just(emptyList()) + + useCase() + .test() + .assertNoErrors() + .assertValue(emptyList()) + .await() + .assertComplete() + + verify { stubPreferenceManager.swarmUiModel = "" } + } + + @Test + fun `given source is SWARM_UI and repository throws error, expected error`() { + val stubException = Throwable("Network error") + + every { stubPreferenceManager.source } returns ServerSource.SWARM_UI + every { stubRepository.fetchAndGetModels() } returns Single.error(stubException) + + useCase() + .test() + .assertError(stubException) + .await() + .assertNotComplete() + } +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCaseImplTest.kt index 997c02636..26a405d6c 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/wakelock/AcquireWakelockUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/wakelock/AcquireWakelockUseCaseImplTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.domain.usecase.wakelock +package dev.minios.pdaiv1.domain.usecase.wakelock import android.os.PowerManager import com.nhaarman.mockitokotlin2.any @@ -6,7 +6,7 @@ import com.nhaarman.mockitokotlin2.doNothing import com.nhaarman.mockitokotlin2.given import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.WakeLockRepository +import dev.minios.pdaiv1.domain.repository.WakeLockRepository import org.junit.Assert import org.junit.Before import org.junit.Test diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImplTest.kt b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImplTest.kt similarity index 91% rename from domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImplTest.kt rename to domain/src/test/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImplTest.kt index 1aae07959..bb5968fd4 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImplTest.kt +++ b/domain/src/test/java/dev/minios/pdaiv1/domain/usecase/wakelock/ReleaseWakeLockUseCaseImplTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.domain.usecase.wakelock +package dev.minios.pdaiv1.domain.usecase.wakelock import android.os.PowerManager import com.nhaarman.mockitokotlin2.doNothing import com.nhaarman.mockitokotlin2.given import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever -import com.shifthackz.aisdv1.domain.repository.WakeLockRepository +import dev.minios.pdaiv1.domain.repository.WakeLockRepository import org.junit.Assert import org.junit.Before import org.junit.Test diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index ed805764a..3aabfda05 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,21 +1,21 @@ -Unleash Your Creativity with SDAI - The Ultimate AI Art Generator for Android +Unleash Your Creativity with PDAI - The Ultimate AI Art Generator for Android -Discover the power of AI-driven art creation with SDAI (Stable Diffusion Android), the open-source app that puts the magic of artificial intelligence at your fingertips. Whether you're a digital artist, a hobbyist, or simply curious about the possibilities of AI, SDAI offers a unique and flexible platform to create stunning, high-quality images with ease. +Discover the power of AI-driven art creation with PDAI (Pocket Diffusion Android), the open-source app that puts the magic of artificial intelligence at your fingertips. Whether you're a digital artist, a hobbyist, or simply curious about the possibilities of AI, PDAI offers a unique and flexible platform to create stunning, high-quality images with ease. -Why Choose SDAI? +Why Choose PDAI? -SDAI isn't just another AI art app—it's a tool that empowers you to create without limits. With the freedom to choose your generation provider and the ability to work offline, you can bring your most ambitious ideas to life, anytime, anywhere. Plus, as an open-source project, you're not just a user—you can be a part of the evolution of SDAI. +PDAI isn't just another AI art app—it's a tool that empowers you to create without limits. With the freedom to choose your generation provider and the ability to work offline, you can bring your most ambitious ideas to life, anytime, anywhere. Plus, as an open-source project, you're not just a user—you can be a part of the evolution of PDAI. Key Features: -- Choose Your AI Generation Provider: SDAI lets you pick the AI model that best suits your needs, giving you complete control over your creative process. Whether you prefer cloud-based solutions or local setups, SDAI has you covered. +- Choose Your AI Generation Provider: PDAI lets you pick the AI model that best suits your needs, giving you complete control over your creative process. Whether you prefer cloud-based solutions or local setups, PDAI has you covered. -- Offline Image Creation with Local Diffusion: No internet? No problem! SDAI enables offline image generation using Local Diffusion, ensuring that your creativity is never interrupted. +- Offline Image Creation with Local Diffusion: No internet? No problem! PDAI enables offline image generation using Local Diffusion, ensuring that your creativity is never interrupted. -- Open Source & Community-Driven: Built with transparency and collaboration in mind, SDAI is fully open source. Join our community of developers and artists, contribute to the project, or simply explore the code to see how it all works. +- Open Source & Community-Driven: Built with transparency and collaboration in mind, PDAI is fully open source. Join our community of developers and artists, contribute to the project, or simply explore the code to see how it all works. -- User-Friendly Interface: Designed with both beginners and experts in mind, SDAI's intuitive interface makes it easy to dive into the world of AI art without any steep learning curve. +- User-Friendly Interface: Designed with both beginners and experts in mind, PDAI's intuitive interface makes it easy to dive into the world of AI art without any steep learning curve. Get Started Today! -Download SDAI now and start exploring the endless possibilities of AI-generated art. Whether you're looking to create intricate digital masterpieces or simply have fun with AI, SDAI is your gateway to a new world of creativity. +Download PDAI now and start exploring the endless possibilities of AI-generated art. Whether you're looking to create intricate digital masterpieces or simply have fun with AI, PDAI is your gateway to a new world of creativity. diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png index 964d230a3..e8fd64d29 100644 Binary files a/fastlane/metadata/android/en-US/images/featureGraphic.png and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png index d037eb100..d940d1738 100644 Binary files a/fastlane/metadata/android/en-US/images/icon.png and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png deleted file mode 100644 index aa942e510..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png and /dev/null differ diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index 17cfb2b96..3ae7abbb1 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.feature.auth" + namespace = "dev.minios.pdaiv1.feature.auth" } dependencies { diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/AuthorizationMappers.kt b/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/AuthorizationMappers.kt deleted file mode 100644 index 43ede32fe..000000000 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/AuthorizationMappers.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.shifthackz.aisdv1.feature.auth - -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.feature.auth.credentials.Credentials -import com.shifthackz.aisdv1.feature.auth.credentials.EmptyCredentials -import com.shifthackz.aisdv1.feature.auth.credentials.HttpBasicCredentials - -internal fun AuthorizationCredentials.toRaw(): Credentials = when (this) { - is AuthorizationCredentials.HttpBasic -> HttpBasicCredentials( - login = login, - password = password, - ) - else -> EmptyCredentials() -} - -internal fun Credentials.toDomain(): AuthorizationCredentials = when { - this is HttpBasicCredentials -> AuthorizationCredentials.HttpBasic( - login = login, - password = password, - ) - else -> AuthorizationCredentials.None -} - -internal fun parseByKeyValueToRaw( - key: AuthorizationCredentials.Key, - rawValue: String -): Credentials = when (key) { - AuthorizationCredentials.Key.NONE -> EmptyCredentials() - AuthorizationCredentials.Key.HTTP_BASIC -> HttpBasicCredentials.fromJson(rawValue) -} diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/Credentials.kt b/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/Credentials.kt deleted file mode 100644 index 96129608b..000000000 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/Credentials.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.feature.auth.credentials - -internal interface Credentials { - fun toJson(): String -} diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/EmptyCredentials.kt b/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/EmptyCredentials.kt deleted file mode 100644 index 935a931e3..000000000 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/EmptyCredentials.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.feature.auth.credentials - -internal class EmptyCredentials : Credentials { - override fun toJson(): String = "" -} diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/crypto/CryptoProvider.kt b/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/crypto/CryptoProvider.kt deleted file mode 100644 index 7fdbd03a4..000000000 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/crypto/CryptoProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.feature.auth.crypto - -import android.content.SharedPreferences - -internal interface CryptoProvider { - fun getAuthorizationPreferences(): SharedPreferences -} diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/di/AuthModule.kt b/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/di/AuthModule.kt deleted file mode 100644 index 07a8c5990..000000000 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/di/AuthModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.feature.auth.di - -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationStore -import com.shifthackz.aisdv1.feature.auth.AuthorizationStoreImpl -import com.shifthackz.aisdv1.feature.auth.crypto.CryptoProvider -import com.shifthackz.aisdv1.feature.auth.crypto.CryptoProviderImpl -import org.koin.android.ext.koin.androidContext -import org.koin.dsl.module - -val authModule = module { - factory { CryptoProviderImpl(androidContext()) } - factory { - val encryptedPreferences = get().getAuthorizationPreferences() - AuthorizationStoreImpl(encryptedPreferences) - } -} diff --git a/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/AuthorizationMappers.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/AuthorizationMappers.kt new file mode 100644 index 000000000..aa239cc53 --- /dev/null +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/AuthorizationMappers.kt @@ -0,0 +1,30 @@ +package dev.minios.pdaiv1.feature.auth + +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.feature.auth.credentials.Credentials +import dev.minios.pdaiv1.feature.auth.credentials.EmptyCredentials +import dev.minios.pdaiv1.feature.auth.credentials.HttpBasicCredentials + +internal fun AuthorizationCredentials.toRaw(): Credentials = when (this) { + is AuthorizationCredentials.HttpBasic -> HttpBasicCredentials( + login = login, + password = password, + ) + else -> EmptyCredentials() +} + +internal fun Credentials.toDomain(): AuthorizationCredentials = when { + this is HttpBasicCredentials -> AuthorizationCredentials.HttpBasic( + login = login, + password = password, + ) + else -> AuthorizationCredentials.None +} + +internal fun parseByKeyValueToRaw( + key: AuthorizationCredentials.Key, + rawValue: String +): Credentials = when (key) { + AuthorizationCredentials.Key.NONE -> EmptyCredentials() + AuthorizationCredentials.Key.HTTP_BASIC -> HttpBasicCredentials.fromJson(rawValue) +} diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/AuthorizationStoreImpl.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/AuthorizationStoreImpl.kt similarity index 88% rename from feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/AuthorizationStoreImpl.kt rename to feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/AuthorizationStoreImpl.kt index b9ef13469..55238b6ae 100644 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/AuthorizationStoreImpl.kt +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/AuthorizationStoreImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.feature.auth +package dev.minios.pdaiv1.feature.auth import android.content.SharedPreferences -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationStore +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationStore internal class AuthorizationStoreImpl( private val preferences: SharedPreferences, diff --git a/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/Credentials.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/Credentials.kt new file mode 100644 index 000000000..f4e7e71e1 --- /dev/null +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/Credentials.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.feature.auth.credentials + +internal interface Credentials { + fun toJson(): String +} diff --git a/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/EmptyCredentials.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/EmptyCredentials.kt new file mode 100644 index 000000000..196b06b50 --- /dev/null +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/EmptyCredentials.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.feature.auth.credentials + +internal class EmptyCredentials : Credentials { + override fun toJson(): String = "" +} diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/HttpBasicCredentials.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/HttpBasicCredentials.kt similarity index 90% rename from feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/HttpBasicCredentials.kt rename to feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/HttpBasicCredentials.kt index 882748a8e..ce33d3838 100644 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/credentials/HttpBasicCredentials.kt +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/credentials/HttpBasicCredentials.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.feature.auth.credentials +package dev.minios.pdaiv1.feature.auth.credentials import com.google.gson.Gson import com.google.gson.annotations.SerializedName diff --git a/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/crypto/CryptoProvider.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/crypto/CryptoProvider.kt new file mode 100644 index 000000000..486df612c --- /dev/null +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/crypto/CryptoProvider.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.feature.auth.crypto + +import android.content.SharedPreferences + +internal interface CryptoProvider { + fun getAuthorizationPreferences(): SharedPreferences +} diff --git a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/crypto/CryptoProviderImpl.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/crypto/CryptoProviderImpl.kt similarity index 87% rename from feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/crypto/CryptoProviderImpl.kt rename to feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/crypto/CryptoProviderImpl.kt index 416f42ae3..0245ce4ec 100644 --- a/feature/auth/src/main/java/com/shifthackz/aisdv1/feature/auth/crypto/CryptoProviderImpl.kt +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/crypto/CryptoProviderImpl.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.feature.auth.crypto +package dev.minios.pdaiv1.feature.auth.crypto import android.content.Context import android.content.SharedPreferences @@ -20,6 +20,6 @@ class CryptoProviderImpl(private val context: Context) : CryptoProvider { private fun getMasterKeyAlias() = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) companion object { - private const val KEY_PREFERENCE_AUTHORIZATION = "sdai_authorization_preference" + private const val KEY_PREFERENCE_AUTHORIZATION = "pdai_authorization_preference" } } diff --git a/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/di/AuthModule.kt b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/di/AuthModule.kt new file mode 100644 index 000000000..f454489a7 --- /dev/null +++ b/feature/auth/src/main/java/dev/minios/pdaiv1/feature/auth/di/AuthModule.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.feature.auth.di + +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationStore +import dev.minios.pdaiv1.feature.auth.AuthorizationStoreImpl +import dev.minios.pdaiv1.feature.auth.crypto.CryptoProvider +import dev.minios.pdaiv1.feature.auth.crypto.CryptoProviderImpl +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val authModule = module { + factory { CryptoProviderImpl(androidContext()) } + factory { + val encryptedPreferences = get().getAuthorizationPreferences() + AuthorizationStoreImpl(encryptedPreferences) + } +} diff --git a/feature/diffusion/build.gradle.kts b/feature/diffusion/build.gradle.kts index 03c679e4a..cf965bf4c 100644 --- a/feature/diffusion/build.gradle.kts +++ b/feature/diffusion/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.feature.diffusion" + namespace = "dev.minios.pdaiv1.feature.diffusion" } dependencies { diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/StringExtensions.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/StringExtensions.kt deleted file mode 100644 index 89519bc9f..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/StringExtensions.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.ai.extensions - -internal fun String.halfCorner(): String { - var output = this - val regs = arrayOf( - "!", ",", "。", ";", "~", "《", "》", "(", ")", "?", - "”", "{", "}", "“", ":", "【", "】", "”", "‘", "’", "!", ",", - ".", ";", "`", "<", ">", "\\(", "\\)", "\\?", "'", "\\{", "}", "\"", - ":", "\\{", "}", "\"", "\'", "\'" - ) - for (i in 0 until (regs.size / 2)) { - output = output.replace(regs[i].toRegex(), regs[i + regs.size / 2]) - } - return output -} - -internal fun String.toArrays(): Array { - val codePoints = codePoints().toArray() - val words = arrayOfNulls(codePoints.size) - for (i in codePoints.indices) { - val code = codePoints[i] - words[i] = String(Character.toChars(code)) - } - return words -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/scheduler/LocalDiffusionScheduler.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/scheduler/LocalDiffusionScheduler.kt deleted file mode 100644 index 3a7f4f3ba..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/scheduler/LocalDiffusionScheduler.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.ai.scheduler - -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionTensor - -internal interface LocalDiffusionScheduler { - val initNoiseSigma: Double - fun setTimeSteps(numInferenceSteps: Int): IntArray - fun scaleModelInput(sample: LocalDiffusionTensor<*>, stepIndex: Int): LocalDiffusionTensor<*> - fun step(modelOutput: LocalDiffusionTensor<*>, stepIndex: Int, sample: LocalDiffusionTensor<*>): LocalDiffusionTensor<*> -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/vae/VaeDecoder.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/vae/VaeDecoder.kt deleted file mode 100644 index 5dea5b554..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/vae/VaeDecoder.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.ai.vae - -import ai.onnxruntime.OnnxTensor -import ai.onnxruntime.OrtSession -import ai.onnxruntime.OrtSession.SessionOptions -import ai.onnxruntime.providers.NNAPIFlags -import android.graphics.Bitmap -import android.graphics.Color -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT -import com.shifthackz.aisdv1.feature.diffusion.entity.Array3D -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionFlag -import com.shifthackz.aisdv1.feature.diffusion.environment.LocalModelIdProvider -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider -import com.shifthackz.aisdv1.feature.diffusion.extensions.modelPathPrefix -import java.util.EnumSet -import kotlin.math.roundToInt - -internal class VaeDecoder( - private val ortEnvironmentProvider: OrtEnvironmentProvider, - private val fileProviderDescriptor: FileProviderDescriptor, - private val localModelIdProvider: LocalModelIdProvider, - private val preferenceManager: PreferenceManager, - private val deviceId: Int, -) { - - private var session: OrtSession? = null - - fun decode(input: Map?): Any { - initialize() - val result = session!!.run(input) - val value = result[0].value - result.close() - close() - return value - } - - fun convertToImage( - output: Array3D, - width: Int, - height: Int, - ): Bitmap { - val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) - for (y in 0 until height) { - for (x in 0 until width) { - val r = (clamp(output[0][0][y][x] / 2 + 0.5) * 255f).roundToInt() - val g = (clamp(output[0][1][y][x] / 2 + 0.5) * 255f).roundToInt() - val b = (clamp(output[0][2][y][x] / 2 + 0.5) * 255f).roundToInt() - val color = Color.rgb(r, g, b) - bitmap.setPixel(x, y, color) - } - } - return bitmap - } - - fun close() { - session?.close() - session = null - } - - private fun initialize() { - if (session != null) return - val options = SessionOptions() - options.addConfigEntry(ORT_KEY_MODEL_FORMAT, ORT) - if (deviceId == LocalDiffusionFlag.NN_API.value) { - options.addNnapi(EnumSet.of(NNAPIFlags.CPU_DISABLED)) - } - session = ortEnvironmentProvider.get().createSession( - "${modelPathPrefix(preferenceManager, fileProviderDescriptor, localModelIdProvider)}/${LocalDiffusionContract.VAE_MODEL}", - options - ) - } - - private fun clamp(value: Double, min: Double = 0.0, max: Double = 1.0): Double = when { - value < min -> min - value > max -> max - else -> value - } -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/di/DiffusionModule.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/di/DiffusionModule.kt deleted file mode 100644 index 4aa7ef14b..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/di/DiffusionModule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.di - -import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionImpl -import com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer.EnglishTextTokenizer -import com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer.LocalDiffusionTextTokenizer -import com.shifthackz.aisdv1.feature.diffusion.ai.unet.UNet -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProviderImpl -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.module - -val diffusionModule = module { - singleOf(::UNet) - singleOf(::EnglishTextTokenizer) bind LocalDiffusionTextTokenizer::class - singleOf(::LocalDiffusionImpl) bind LocalDiffusion::class - singleOf(::OrtEnvironmentProviderImpl) bind OrtEnvironmentProvider::class -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionAlias.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionAlias.kt deleted file mode 100644 index debe29cb4..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionAlias.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.entity - -typealias Array3D = Array>> diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionConfig.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionConfig.kt deleted file mode 100644 index 5d6728083..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionConfig.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.entity - -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_SCALED_LINEAR -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.DPM_SOLVER_PP -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.PREDICTION_EPSILON -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.SOLVER_MIDPOINT - -internal data class LocalDiffusionConfig( - val betaStart: Float = 0.00085f, - val betaEnd: Float = 0.012f, - val betaSchedule: String = BETA_SCHEDULER_SCALED_LINEAR, - val trainedBetas: List = emptyList(), - val solverOrder: Int = 2, - val predictionType: String = PREDICTION_EPSILON, - val thresholding: Boolean = false, - val dynamicThresholdingRatio: Float = 0.995f, - val sampleMaxValue: Float = 1.0f, - val algorithmType: String = DPM_SOLVER_PP, - val solverType: String = SOLVER_MIDPOINT, - val lowerOrderFinal: Boolean = true, - val clipSample: Boolean = false, - val clipSampleRange: Float = 1.0f, -) diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionFlag.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionFlag.kt deleted file mode 100644 index 34479d859..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionFlag.kt +++ /dev/null @@ -1,8 +0,0 @@ -@file:Suppress("unused") - -package com.shifthackz.aisdv1.feature.diffusion.entity - -enum class LocalDiffusionFlag(val value: Int) { - CPU(0), - NN_API(1); -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/DeviceNNAPIFlagProvider.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/DeviceNNAPIFlagProvider.kt deleted file mode 100644 index 692b24b2a..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/DeviceNNAPIFlagProvider.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.environment - -fun interface DeviceNNAPIFlagProvider { - fun get(): Int -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/LocalModelIdProvider.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/LocalModelIdProvider.kt deleted file mode 100644 index 3c24f58bf..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/LocalModelIdProvider.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.environment - -fun interface LocalModelIdProvider { - fun get(): String -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/OrtEnvironmentProvider.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/OrtEnvironmentProvider.kt deleted file mode 100644 index 2d6666601..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/OrtEnvironmentProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.environment - -import ai.onnxruntime.OrtEnvironment - -internal fun interface OrtEnvironmentProvider { - fun get(): OrtEnvironment -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/extensions/LocalDiffusionPaths.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/extensions/LocalDiffusionPaths.kt deleted file mode 100644 index 1b22e6d94..000000000 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/extensions/LocalDiffusionPaths.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.shifthackz.aisdv1.feature.diffusion.extensions - -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.feature.diffusion.environment.LocalModelIdProvider - -fun modelPathPrefix( - preferenceManager: PreferenceManager, - fileProviderDescriptor: FileProviderDescriptor, - localModelIdProvider: LocalModelIdProvider, -): String { - val modelId = localModelIdProvider.get() - return if (modelId == LocalAiModel.CustomOnnx.id) { - preferenceManager.localOnnxCustomModelPath - } else { - "${fileProviderDescriptor.localModelDirPath}/${modelId}" - } -} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/LocalDiffusionContract.kt similarity index 96% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/LocalDiffusionContract.kt index a59a7afce..f1b068ca0 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/LocalDiffusionContract.kt @@ -1,6 +1,6 @@ @file:Suppress("SpellCheckingInspection") -package com.shifthackz.aisdv1.feature.diffusion +package dev.minios.pdaiv1.feature.diffusion internal object LocalDiffusionContract { //region LOGGING diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/LocalDiffusionImpl.kt similarity index 85% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/LocalDiffusionImpl.kt index 775cad2c2..0ece29d23 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/LocalDiffusionImpl.kt @@ -1,16 +1,16 @@ -package com.shifthackz.aisdv1.feature.diffusion +package dev.minios.pdaiv1.feature.diffusion import ai.onnxruntime.OnnxTensor import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG -import com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer.LocalDiffusionTextTokenizer -import com.shifthackz.aisdv1.feature.diffusion.ai.unet.UNet -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.diffusion.LocalDiffusion +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.TAG +import dev.minios.pdaiv1.feature.diffusion.ai.tokenizer.LocalDiffusionTextTokenizer +import dev.minios.pdaiv1.feature.diffusion.ai.unet.UNet +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProvider import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.subjects.PublishSubject diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/ArrayExtensions.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/ArrayExtensions.kt similarity index 93% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/ArrayExtensions.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/ArrayExtensions.kt index 5f844ee72..d679bc28e 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/ArrayExtensions.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/ArrayExtensions.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.feature.diffusion.ai.extensions +package dev.minios.pdaiv1.feature.diffusion.ai.extensions -import com.shifthackz.aisdv1.feature.diffusion.entity.Array3D +import dev.minios.pdaiv1.feature.diffusion.entity.Array3D import kotlin.math.ceil import java.util.function.Function diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/StringExtensions.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/StringExtensions.kt new file mode 100644 index 000000000..461086475 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/StringExtensions.kt @@ -0,0 +1,25 @@ +package dev.minios.pdaiv1.feature.diffusion.ai.extensions + +internal fun String.halfCorner(): String { + var output = this + val regs = arrayOf( + "!", ",", "。", ";", "~", "《", "》", "(", ")", "?", + "”", "{", "}", "“", ":", "【", "】", "”", "‘", "’", "!", ",", + ".", ";", "`", "<", ">", "\\(", "\\)", "\\?", "'", "\\{", "}", "\"", + ":", "\\{", "}", "\"", "\'", "\'" + ) + for (i in 0 until (regs.size / 2)) { + output = output.replace(regs[i].toRegex(), regs[i + regs.size / 2]) + } + return output +} + +internal fun String.toArrays(): Array { + val codePoints = codePoints().toArray() + val words = arrayOfNulls(codePoints.size) + for (i in codePoints.indices) { + val code = codePoints[i] + words[i] = String(Character.toChars(code)) + } + return words +} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/TensorExtensions.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/TensorExtensions.kt similarity index 89% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/TensorExtensions.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/TensorExtensions.kt index 97ba816ba..b1cff7b52 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/extensions/TensorExtensions.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/extensions/TensorExtensions.kt @@ -1,12 +1,12 @@ @file:Suppress("KotlinConstantConditions") -package com.shifthackz.aisdv1.feature.diffusion.ai.extensions +package dev.minios.pdaiv1.feature.diffusion.ai.extensions import ai.onnxruntime.OnnxTensor import android.util.Pair -import com.shifthackz.aisdv1.feature.diffusion.entity.Array3D -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionTensor -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider +import dev.minios.pdaiv1.feature.diffusion.entity.Array3D +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionTensor +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProvider import org.koin.java.KoinJavaComponent.inject import java.nio.FloatBuffer diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/scheduler/EulerAncestralDiscreteLocalDiffusionScheduler.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/scheduler/EulerAncestralDiscreteLocalDiffusionScheduler.kt similarity index 90% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/scheduler/EulerAncestralDiscreteLocalDiffusionScheduler.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/scheduler/EulerAncestralDiscreteLocalDiffusionScheduler.kt index 68722b009..ab28f9984 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/scheduler/EulerAncestralDiscreteLocalDiffusionScheduler.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/scheduler/EulerAncestralDiscreteLocalDiffusionScheduler.kt @@ -1,20 +1,20 @@ @file:Suppress("UNCHECKED_CAST") -package com.shifthackz.aisdv1.feature.diffusion.ai.scheduler +package dev.minios.pdaiv1.feature.diffusion.ai.scheduler import ai.onnxruntime.OnnxTensor -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_LINEAR -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_SCALED_LINEAR -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_SQUARED_v2 -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.PREDICTION_EPSILON -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.PREDICTION_V -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.arrange -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.interpolate -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.lineSpace -import com.shifthackz.aisdv1.feature.diffusion.entity.Array3D -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionTensor -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionConfig +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_LINEAR +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_SCALED_LINEAR +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_SQUARED_v2 +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.PREDICTION_EPSILON +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.PREDICTION_V +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.arrange +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.interpolate +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.lineSpace +import dev.minios.pdaiv1.feature.diffusion.entity.Array3D +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionTensor +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProvider +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionConfig import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.nio.FloatBuffer diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/scheduler/LocalDiffusionScheduler.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/scheduler/LocalDiffusionScheduler.kt new file mode 100644 index 000000000..fe805c819 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/scheduler/LocalDiffusionScheduler.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.feature.diffusion.ai.scheduler + +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionTensor + +internal interface LocalDiffusionScheduler { + val initNoiseSigma: Double + fun setTimeSteps(numInferenceSteps: Int): IntArray + fun scaleModelInput(sample: LocalDiffusionTensor<*>, stepIndex: Int): LocalDiffusionTensor<*> + fun step(modelOutput: LocalDiffusionTensor<*>, stepIndex: Int, sample: LocalDiffusionTensor<*>): LocalDiffusionTensor<*> +} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt similarity index 90% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt index 0683aeff5..c15e322f4 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt @@ -1,26 +1,26 @@ @file:Suppress("KotlinConstantConditions") -package com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer +package dev.minios.pdaiv1.feature.diffusion.ai.tokenizer import ai.onnxruntime.OnnxTensor import ai.onnxruntime.OrtSession import android.text.TextUtils import android.util.JsonReader import android.util.Pair -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_INPUT_IDS -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.halfCorner -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.toArrays -import com.shifthackz.aisdv1.feature.diffusion.environment.LocalModelIdProvider -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider -import com.shifthackz.aisdv1.feature.diffusion.extensions.modelPathPrefix +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.KEY_INPUT_IDS +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.ORT +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.TAG +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.halfCorner +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.toArrays +import dev.minios.pdaiv1.feature.diffusion.environment.LocalModelIdProvider +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProvider +import dev.minios.pdaiv1.feature.diffusion.extensions.modelPathPrefix import java.io.BufferedReader import java.io.FileInputStream import java.io.InputStreamReader diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/LocalDiffusionTextTokenizer.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/LocalDiffusionTextTokenizer.kt similarity index 84% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/LocalDiffusionTextTokenizer.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/LocalDiffusionTextTokenizer.kt index ee66bfdb8..da7178be2 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/LocalDiffusionTextTokenizer.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/LocalDiffusionTextTokenizer.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer +package dev.minios.pdaiv1.feature.diffusion.ai.tokenizer import ai.onnxruntime.OnnxTensor diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/TokenizerByteSet.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/TokenizerByteSet.kt similarity index 99% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/TokenizerByteSet.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/TokenizerByteSet.kt index 615b55489..ecaf9d28b 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/TokenizerByteSet.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/tokenizer/TokenizerByteSet.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer +package dev.minios.pdaiv1.feature.diffusion.ai.tokenizer object TokenizerByteSet { val byteEncoder: MutableMap = HashMap() diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/unet/UNet.kt similarity index 85% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/unet/UNet.kt index 85cc131fd..bdc6a544b 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/unet/UNet.kt @@ -1,6 +1,6 @@ @file:Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate") -package com.shifthackz.aisdv1.feature.diffusion.ai.unet +package dev.minios.pdaiv1.feature.diffusion.ai.unet import ai.onnxruntime.OnnxTensor import ai.onnxruntime.OrtSession @@ -8,30 +8,30 @@ import ai.onnxruntime.OrtSession.SessionOptions import ai.onnxruntime.providers.NNAPIFlags import android.graphics.Bitmap import android.util.Pair -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_ENCODER_HIDDEN_STATES -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_LATENT_SAMPLE -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_SAMPLE -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_TIME_STEP -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT -import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.duplicate -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.getSizes -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.multipleTensorsByFloat -import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.splitTensor -import com.shifthackz.aisdv1.feature.diffusion.ai.scheduler.EulerAncestralDiscreteLocalDiffusionScheduler -import com.shifthackz.aisdv1.feature.diffusion.ai.vae.VaeDecoder -import com.shifthackz.aisdv1.feature.diffusion.entity.Array3D -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionFlag -import com.shifthackz.aisdv1.feature.diffusion.entity.LocalDiffusionTensor -import com.shifthackz.aisdv1.feature.diffusion.environment.DeviceNNAPIFlagProvider -import com.shifthackz.aisdv1.feature.diffusion.environment.LocalModelIdProvider -import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider -import com.shifthackz.aisdv1.feature.diffusion.extensions.modelPathPrefix +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.KEY_ENCODER_HIDDEN_STATES +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.KEY_LATENT_SAMPLE +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.KEY_SAMPLE +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.KEY_TIME_STEP +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.ORT +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.TAG +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.duplicate +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.getSizes +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.multipleTensorsByFloat +import dev.minios.pdaiv1.feature.diffusion.ai.extensions.splitTensor +import dev.minios.pdaiv1.feature.diffusion.ai.scheduler.EulerAncestralDiscreteLocalDiffusionScheduler +import dev.minios.pdaiv1.feature.diffusion.ai.vae.VaeDecoder +import dev.minios.pdaiv1.feature.diffusion.entity.Array3D +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionFlag +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionTensor +import dev.minios.pdaiv1.feature.diffusion.environment.DeviceNNAPIFlagProvider +import dev.minios.pdaiv1.feature.diffusion.environment.LocalModelIdProvider +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProvider +import dev.minios.pdaiv1.feature.diffusion.extensions.modelPathPrefix import java.nio.IntBuffer import java.util.EnumSet import java.util.Random diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/vae/VaeDecoder.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/vae/VaeDecoder.kt new file mode 100644 index 000000000..43bcf8316 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/ai/vae/VaeDecoder.kt @@ -0,0 +1,82 @@ +package dev.minios.pdaiv1.feature.diffusion.ai.vae + +import ai.onnxruntime.OnnxTensor +import ai.onnxruntime.OrtSession +import ai.onnxruntime.OrtSession.SessionOptions +import ai.onnxruntime.providers.NNAPIFlags +import android.graphics.Bitmap +import android.graphics.Color +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.ORT +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT +import dev.minios.pdaiv1.feature.diffusion.entity.Array3D +import dev.minios.pdaiv1.feature.diffusion.entity.LocalDiffusionFlag +import dev.minios.pdaiv1.feature.diffusion.environment.LocalModelIdProvider +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProvider +import dev.minios.pdaiv1.feature.diffusion.extensions.modelPathPrefix +import java.util.EnumSet +import kotlin.math.roundToInt + +internal class VaeDecoder( + private val ortEnvironmentProvider: OrtEnvironmentProvider, + private val fileProviderDescriptor: FileProviderDescriptor, + private val localModelIdProvider: LocalModelIdProvider, + private val preferenceManager: PreferenceManager, + private val deviceId: Int, +) { + + private var session: OrtSession? = null + + fun decode(input: Map?): Any { + initialize() + val result = session!!.run(input) + val value = result[0].value + result.close() + close() + return value + } + + fun convertToImage( + output: Array3D, + width: Int, + height: Int, + ): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) + for (y in 0 until height) { + for (x in 0 until width) { + val r = (clamp(output[0][0][y][x] / 2 + 0.5) * 255f).roundToInt() + val g = (clamp(output[0][1][y][x] / 2 + 0.5) * 255f).roundToInt() + val b = (clamp(output[0][2][y][x] / 2 + 0.5) * 255f).roundToInt() + val color = Color.rgb(r, g, b) + bitmap.setPixel(x, y, color) + } + } + return bitmap + } + + fun close() { + session?.close() + session = null + } + + private fun initialize() { + if (session != null) return + val options = SessionOptions() + options.addConfigEntry(ORT_KEY_MODEL_FORMAT, ORT) + if (deviceId == LocalDiffusionFlag.NN_API.value) { + options.addNnapi(EnumSet.of(NNAPIFlags.CPU_DISABLED)) + } + session = ortEnvironmentProvider.get().createSession( + "${modelPathPrefix(preferenceManager, fileProviderDescriptor, localModelIdProvider)}/${LocalDiffusionContract.VAE_MODEL}", + options + ) + } + + private fun clamp(value: Double, min: Double = 0.0, max: Double = 1.0): Double = when { + value < min -> min + value > max -> max + else -> value + } +} diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/di/DiffusionModule.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/di/DiffusionModule.kt new file mode 100644 index 000000000..2f49c7055 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/di/DiffusionModule.kt @@ -0,0 +1,19 @@ +package dev.minios.pdaiv1.feature.diffusion.di + +import dev.minios.pdaiv1.domain.feature.diffusion.LocalDiffusion +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionImpl +import dev.minios.pdaiv1.feature.diffusion.ai.tokenizer.EnglishTextTokenizer +import dev.minios.pdaiv1.feature.diffusion.ai.tokenizer.LocalDiffusionTextTokenizer +import dev.minios.pdaiv1.feature.diffusion.ai.unet.UNet +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProvider +import dev.minios.pdaiv1.feature.diffusion.environment.OrtEnvironmentProviderImpl +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val diffusionModule = module { + singleOf(::UNet) + singleOf(::EnglishTextTokenizer) bind LocalDiffusionTextTokenizer::class + singleOf(::LocalDiffusionImpl) bind LocalDiffusion::class + singleOf(::OrtEnvironmentProviderImpl) bind OrtEnvironmentProvider::class +} diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionAlias.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionAlias.kt new file mode 100644 index 000000000..e4e62bdf0 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionAlias.kt @@ -0,0 +1,3 @@ +package dev.minios.pdaiv1.feature.diffusion.entity + +typealias Array3D = Array>> diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionConfig.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionConfig.kt new file mode 100644 index 000000000..93960b9a9 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionConfig.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.feature.diffusion.entity + +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.BETA_SCHEDULER_SCALED_LINEAR +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.DPM_SOLVER_PP +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.PREDICTION_EPSILON +import dev.minios.pdaiv1.feature.diffusion.LocalDiffusionContract.SOLVER_MIDPOINT + +internal data class LocalDiffusionConfig( + val betaStart: Float = 0.00085f, + val betaEnd: Float = 0.012f, + val betaSchedule: String = BETA_SCHEDULER_SCALED_LINEAR, + val trainedBetas: List = emptyList(), + val solverOrder: Int = 2, + val predictionType: String = PREDICTION_EPSILON, + val thresholding: Boolean = false, + val dynamicThresholdingRatio: Float = 0.995f, + val sampleMaxValue: Float = 1.0f, + val algorithmType: String = DPM_SOLVER_PP, + val solverType: String = SOLVER_MIDPOINT, + val lowerOrderFinal: Boolean = true, + val clipSample: Boolean = false, + val clipSampleRange: Float = 1.0f, +) diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionFlag.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionFlag.kt new file mode 100644 index 000000000..7a71019fe --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionFlag.kt @@ -0,0 +1,8 @@ +@file:Suppress("unused") + +package dev.minios.pdaiv1.feature.diffusion.entity + +enum class LocalDiffusionFlag(val value: Int) { + CPU(0), + NN_API(1); +} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionTensor.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionTensor.kt similarity index 94% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionTensor.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionTensor.kt index 3287de5f4..68a509aa8 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/entity/LocalDiffusionTensor.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/entity/LocalDiffusionTensor.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.feature.diffusion.entity +package dev.minios.pdaiv1.feature.diffusion.entity import ai.onnxruntime.OnnxTensor diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/DeviceNNAPIFlagProvider.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/DeviceNNAPIFlagProvider.kt new file mode 100644 index 000000000..10cb6ea60 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/DeviceNNAPIFlagProvider.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.feature.diffusion.environment + +fun interface DeviceNNAPIFlagProvider { + fun get(): Int +} diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/LocalModelIdProvider.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/LocalModelIdProvider.kt new file mode 100644 index 000000000..e139a2de1 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/LocalModelIdProvider.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.feature.diffusion.environment + +fun interface LocalModelIdProvider { + fun get(): String +} diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/OrtEnvironmentProvider.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/OrtEnvironmentProvider.kt new file mode 100644 index 000000000..32ca618e6 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/OrtEnvironmentProvider.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.feature.diffusion.environment + +import ai.onnxruntime.OrtEnvironment + +internal fun interface OrtEnvironmentProvider { + fun get(): OrtEnvironment +} diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/OrtEnvironmentProviderImpl.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/OrtEnvironmentProviderImpl.kt similarity index 81% rename from feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/OrtEnvironmentProviderImpl.kt rename to feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/OrtEnvironmentProviderImpl.kt index cc680035a..c4b66cc74 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/environment/OrtEnvironmentProviderImpl.kt +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/environment/OrtEnvironmentProviderImpl.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.feature.diffusion.environment +package dev.minios.pdaiv1.feature.diffusion.environment import ai.onnxruntime.OrtEnvironment diff --git a/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/extensions/LocalDiffusionPaths.kt b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/extensions/LocalDiffusionPaths.kt new file mode 100644 index 000000000..0a0395896 --- /dev/null +++ b/feature/diffusion/src/main/java/dev/minios/pdaiv1/feature/diffusion/extensions/LocalDiffusionPaths.kt @@ -0,0 +1,19 @@ +package dev.minios.pdaiv1.feature.diffusion.extensions + +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.diffusion.environment.LocalModelIdProvider + +fun modelPathPrefix( + preferenceManager: PreferenceManager, + fileProviderDescriptor: FileProviderDescriptor, + localModelIdProvider: LocalModelIdProvider, +): String { + val modelId = localModelIdProvider.get() + return if (modelId == LocalAiModel.CustomOnnx.id) { + preferenceManager.localOnnxCustomModelPath + } else { + "${fileProviderDescriptor.localModelDirPath}/${modelId}" + } +} diff --git a/feature/mediapipe/build.gradle.kts b/feature/mediapipe/build.gradle.kts index b58a73581..77f1a2844 100644 --- a/feature/mediapipe/build.gradle.kts +++ b/feature/mediapipe/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.feature.mediapipe" + namespace = "dev.minios.pdaiv1.feature.mediapipe" } dependencies { diff --git a/feature/mediapipe/src/foss/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt b/feature/mediapipe/src/foss/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt deleted file mode 100644 index 3be83e5ea..000000000 --- a/feature/mediapipe/src/foss/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.feature.mediapipe - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.mediapipe.MediaPipe -import io.reactivex.rxjava3.core.Single - -internal class MediaPipeImpl : MediaPipe { - - override fun process(payload: TextToImagePayload): Single { - return Single.error(IllegalStateException("Google AI MediaPipe is not supported on FOSS build.")) - } -} diff --git a/feature/mediapipe/src/foss/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt b/feature/mediapipe/src/foss/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt new file mode 100644 index 000000000..eaa2121c9 --- /dev/null +++ b/feature/mediapipe/src/foss/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.feature.mediapipe + +import android.graphics.Bitmap +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.mediapipe.MediaPipe +import io.reactivex.rxjava3.core.Single + +internal class MediaPipeImpl : MediaPipe { + + override fun process(payload: TextToImagePayload): Single { + return Single.error(IllegalStateException("Google AI MediaPipe is not supported on FOSS build.")) + } +} diff --git a/feature/mediapipe/src/full/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt b/feature/mediapipe/src/full/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt deleted file mode 100644 index a6e7e98ab..000000000 --- a/feature/mediapipe/src/full/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.shifthackz.aisdv1.feature.mediapipe - -import android.content.Context -import android.graphics.Bitmap -import com.google.mediapipe.framework.image.BitmapExtractor -import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator -import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator.ImageGeneratorOptions -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.mediapipe.MediaPipe -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.feature.mediapipe.extensions.modelPath -import io.reactivex.rxjava3.core.Single - -internal class MediaPipeImpl( - private val context: Context, - private val preferenceManager: PreferenceManager, - private val fileProviderDescriptor: FileProviderDescriptor, -) : MediaPipe { - - private var imageGenerator: ImageGenerator? = null - - override fun process(payload: TextToImagePayload): Single = Single.create { emitter -> - try { - initialize() - debugLog("Generating...") - val result = imageGenerator?.generate( - payload.prompt, - payload.samplingSteps, - payload.seed.toIntOrNull() ?: 0, - ) - debugLog("Extracting bitmap...") - val bitmap = BitmapExtractor.extract(result?.generatedImage()) - debugLog("bitmap = $bitmap, ${bitmap.width}X${bitmap.height}") - close() - if (!emitter.isDisposed) emitter.onSuccess(bitmap) - } catch (e: Exception) { - close() - if (!emitter.isDisposed) emitter.onError(e) - } - } - - private fun initialize(): ImageGenerator { - val path = modelPath(preferenceManager, fileProviderDescriptor) - - val options = ImageGeneratorOptions.builder() - .setImageGeneratorModelDirectory(path) - .build() - - val generator = ImageGenerator.createFromOptions(context, options) - imageGenerator = generator - debugLog("Initialized successfully! Path: $path") - return generator - } - - private fun close() = runCatching { - debugLog("Closing...") - imageGenerator?.close() - imageGenerator = null - debugLog("Session closed!") - } -} diff --git a/feature/mediapipe/src/full/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt b/feature/mediapipe/src/full/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt new file mode 100644 index 000000000..fa4cf5de9 --- /dev/null +++ b/feature/mediapipe/src/full/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt @@ -0,0 +1,63 @@ +package dev.minios.pdaiv1.feature.mediapipe + +import android.content.Context +import android.graphics.Bitmap +import com.google.mediapipe.framework.image.BitmapExtractor +import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator +import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator.ImageGeneratorOptions +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.mediapipe.MediaPipe +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.mediapipe.extensions.modelPath +import io.reactivex.rxjava3.core.Single + +internal class MediaPipeImpl( + private val context: Context, + private val preferenceManager: PreferenceManager, + private val fileProviderDescriptor: FileProviderDescriptor, +) : MediaPipe { + + private var imageGenerator: ImageGenerator? = null + + override fun process(payload: TextToImagePayload): Single = Single.create { emitter -> + try { + initialize() + debugLog("Generating...") + val result = imageGenerator?.generate( + payload.prompt, + payload.samplingSteps, + payload.seed.toIntOrNull() ?: 0, + ) + debugLog("Extracting bitmap...") + val bitmap = BitmapExtractor.extract(result?.generatedImage()) + debugLog("bitmap = $bitmap, ${bitmap.width}X${bitmap.height}") + close() + if (!emitter.isDisposed) emitter.onSuccess(bitmap) + } catch (e: Exception) { + close() + if (!emitter.isDisposed) emitter.onError(e) + } + } + + private fun initialize(): ImageGenerator { + val path = modelPath(preferenceManager, fileProviderDescriptor) + + val options = ImageGeneratorOptions.builder() + .setImageGeneratorModelDirectory(path) + .build() + + val generator = ImageGenerator.createFromOptions(context, options) + imageGenerator = generator + debugLog("Initialized successfully! Path: $path") + return generator + } + + private fun close() = runCatching { + debugLog("Closing...") + imageGenerator?.close() + imageGenerator = null + debugLog("Session closed!") + } +} diff --git a/feature/mediapipe/src/main/java/com/shifthackz/aisdv1/feature/mediapipe/di/MediaPipeModule.kt b/feature/mediapipe/src/main/java/com/shifthackz/aisdv1/feature/mediapipe/di/MediaPipeModule.kt deleted file mode 100644 index 3327827e3..000000000 --- a/feature/mediapipe/src/main/java/com/shifthackz/aisdv1/feature/mediapipe/di/MediaPipeModule.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.feature.mediapipe.di - -import com.shifthackz.aisdv1.domain.feature.mediapipe.MediaPipe -import com.shifthackz.aisdv1.feature.mediapipe.MediaPipeImpl -import org.koin.core.module.dsl.factoryOf -import org.koin.dsl.bind -import org.koin.dsl.module - -val mediaPipeModule = module { - factoryOf(::MediaPipeImpl) bind MediaPipe::class -} diff --git a/feature/mediapipe/src/main/java/com/shifthackz/aisdv1/feature/mediapipe/extensions/MediaPipeModelPaths.kt b/feature/mediapipe/src/main/java/com/shifthackz/aisdv1/feature/mediapipe/extensions/MediaPipeModelPaths.kt deleted file mode 100644 index 91e494047..000000000 --- a/feature/mediapipe/src/main/java/com/shifthackz/aisdv1/feature/mediapipe/extensions/MediaPipeModelPaths.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.feature.mediapipe.extensions - -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager - -fun modelPath( - preferenceManager: PreferenceManager, - fileProviderDescriptor: FileProviderDescriptor, -): String { - val modelId = preferenceManager.localMediaPipeModelId - return if (modelId == LocalAiModel.CustomMediaPipe.id) { - preferenceManager.localMediaPipeCustomModelPath - } else { - "${fileProviderDescriptor.localModelDirPath}/${modelId}" - } -} diff --git a/feature/mediapipe/src/main/java/dev/minios/pdaiv1/feature/mediapipe/di/MediaPipeModule.kt b/feature/mediapipe/src/main/java/dev/minios/pdaiv1/feature/mediapipe/di/MediaPipeModule.kt new file mode 100644 index 000000000..fbf3ca12d --- /dev/null +++ b/feature/mediapipe/src/main/java/dev/minios/pdaiv1/feature/mediapipe/di/MediaPipeModule.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.feature.mediapipe.di + +import dev.minios.pdaiv1.domain.feature.mediapipe.MediaPipe +import dev.minios.pdaiv1.feature.mediapipe.MediaPipeImpl +import org.koin.core.module.dsl.factoryOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val mediaPipeModule = module { + factoryOf(::MediaPipeImpl) bind MediaPipe::class +} diff --git a/feature/mediapipe/src/main/java/dev/minios/pdaiv1/feature/mediapipe/extensions/MediaPipeModelPaths.kt b/feature/mediapipe/src/main/java/dev/minios/pdaiv1/feature/mediapipe/extensions/MediaPipeModelPaths.kt new file mode 100644 index 000000000..35d8cb020 --- /dev/null +++ b/feature/mediapipe/src/main/java/dev/minios/pdaiv1/feature/mediapipe/extensions/MediaPipeModelPaths.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.feature.mediapipe.extensions + +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager + +fun modelPath( + preferenceManager: PreferenceManager, + fileProviderDescriptor: FileProviderDescriptor, +): String { + val modelId = preferenceManager.localMediaPipeModelId + return if (modelId == LocalAiModel.CustomMediaPipe.id) { + preferenceManager.localMediaPipeCustomModelPath + } else { + "${fileProviderDescriptor.localModelDirPath}/${modelId}" + } +} diff --git a/feature/mediapipe/src/playstore/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt b/feature/mediapipe/src/playstore/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt deleted file mode 100644 index a6e7e98ab..000000000 --- a/feature/mediapipe/src/playstore/java/com/shifthackz/aisdv1/feature/mediapipe/MediaPipeImpl.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.shifthackz.aisdv1.feature.mediapipe - -import android.content.Context -import android.graphics.Bitmap -import com.google.mediapipe.framework.image.BitmapExtractor -import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator -import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator.ImageGeneratorOptions -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.mediapipe.MediaPipe -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.feature.mediapipe.extensions.modelPath -import io.reactivex.rxjava3.core.Single - -internal class MediaPipeImpl( - private val context: Context, - private val preferenceManager: PreferenceManager, - private val fileProviderDescriptor: FileProviderDescriptor, -) : MediaPipe { - - private var imageGenerator: ImageGenerator? = null - - override fun process(payload: TextToImagePayload): Single = Single.create { emitter -> - try { - initialize() - debugLog("Generating...") - val result = imageGenerator?.generate( - payload.prompt, - payload.samplingSteps, - payload.seed.toIntOrNull() ?: 0, - ) - debugLog("Extracting bitmap...") - val bitmap = BitmapExtractor.extract(result?.generatedImage()) - debugLog("bitmap = $bitmap, ${bitmap.width}X${bitmap.height}") - close() - if (!emitter.isDisposed) emitter.onSuccess(bitmap) - } catch (e: Exception) { - close() - if (!emitter.isDisposed) emitter.onError(e) - } - } - - private fun initialize(): ImageGenerator { - val path = modelPath(preferenceManager, fileProviderDescriptor) - - val options = ImageGeneratorOptions.builder() - .setImageGeneratorModelDirectory(path) - .build() - - val generator = ImageGenerator.createFromOptions(context, options) - imageGenerator = generator - debugLog("Initialized successfully! Path: $path") - return generator - } - - private fun close() = runCatching { - debugLog("Closing...") - imageGenerator?.close() - imageGenerator = null - debugLog("Session closed!") - } -} diff --git a/feature/mediapipe/src/playstore/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt b/feature/mediapipe/src/playstore/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt new file mode 100644 index 000000000..fa4cf5de9 --- /dev/null +++ b/feature/mediapipe/src/playstore/java/dev/minios/pdaiv1/feature/mediapipe/MediaPipeImpl.kt @@ -0,0 +1,63 @@ +package dev.minios.pdaiv1.feature.mediapipe + +import android.content.Context +import android.graphics.Bitmap +import com.google.mediapipe.framework.image.BitmapExtractor +import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator +import com.google.mediapipe.tasks.vision.imagegenerator.ImageGenerator.ImageGeneratorOptions +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.mediapipe.MediaPipe +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.mediapipe.extensions.modelPath +import io.reactivex.rxjava3.core.Single + +internal class MediaPipeImpl( + private val context: Context, + private val preferenceManager: PreferenceManager, + private val fileProviderDescriptor: FileProviderDescriptor, +) : MediaPipe { + + private var imageGenerator: ImageGenerator? = null + + override fun process(payload: TextToImagePayload): Single = Single.create { emitter -> + try { + initialize() + debugLog("Generating...") + val result = imageGenerator?.generate( + payload.prompt, + payload.samplingSteps, + payload.seed.toIntOrNull() ?: 0, + ) + debugLog("Extracting bitmap...") + val bitmap = BitmapExtractor.extract(result?.generatedImage()) + debugLog("bitmap = $bitmap, ${bitmap.width}X${bitmap.height}") + close() + if (!emitter.isDisposed) emitter.onSuccess(bitmap) + } catch (e: Exception) { + close() + if (!emitter.isDisposed) emitter.onError(e) + } + } + + private fun initialize(): ImageGenerator { + val path = modelPath(preferenceManager, fileProviderDescriptor) + + val options = ImageGeneratorOptions.builder() + .setImageGeneratorModelDirectory(path) + .build() + + val generator = ImageGenerator.createFromOptions(context, options) + imageGenerator = generator + debugLog("Initialized successfully! Path: $path") + return generator + } + + private fun close() = runCatching { + debugLog("Closing...") + imageGenerator?.close() + imageGenerator = null + debugLog("Session closed!") + } +} diff --git a/feature/mediapipe/src/test/java/com/shifthackz/aisdv1/feature/mediapipe/ExampleUnitTest.kt b/feature/mediapipe/src/test/java/dev/minios/pdaiv1/feature/mediapipe/ExampleUnitTest.kt similarity index 86% rename from feature/mediapipe/src/test/java/com/shifthackz/aisdv1/feature/mediapipe/ExampleUnitTest.kt rename to feature/mediapipe/src/test/java/dev/minios/pdaiv1/feature/mediapipe/ExampleUnitTest.kt index 2fc2001c4..036920cba 100644 --- a/feature/mediapipe/src/test/java/com/shifthackz/aisdv1/feature/mediapipe/ExampleUnitTest.kt +++ b/feature/mediapipe/src/test/java/dev/minios/pdaiv1/feature/mediapipe/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.feature.mediapipe +package dev.minios.pdaiv1.feature.mediapipe import org.junit.Assert.* import org.junit.Test diff --git a/feature/qnn/README.md b/feature/qnn/README.md new file mode 100644 index 000000000..cb522d8b4 --- /dev/null +++ b/feature/qnn/README.md @@ -0,0 +1,206 @@ +# Qualcomm QNN Feature Module + +This module provides local Stable Diffusion inference using Qualcomm's Neural Processing Unit (NPU) via the QNN SDK. + +## Overview + +The QNN module enables on-device image generation without internet connection, leveraging the dedicated AI hardware in Qualcomm Snapdragon processors. + +### Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Android App │ +├─────────────────────────────────────────────────────────┤ +│ LocalQnn (Kotlin) │ +│ ├── QnnGenerationService (HTTP Client) │ +│ └── QnnBackendService (Foreground Service) │ +├─────────────────────────────────────────────────────────┤ +│ libstable_diffusion_core.so (C++ HTTP Server) │ +│ ├── MNN Framework (CPU/GPU fallback) │ +│ └── QNN SDK (NPU acceleration) │ +├─────────────────────────────────────────────────────────┤ +│ Qualcomm HTP (Hexagon Tensor Processor) │ +└─────────────────────────────────────────────────────────┘ +``` + +## Supported Devices + +| Chipset | SoC | QNN Version | Status | +|---------|-----|-------------|--------| +| Snapdragon 8 Gen 1 | SM8450 | V68 | ✅ Supported | +| Snapdragon 8+ Gen 1 | SM8475 | V69 | ✅ Supported | +| Snapdragon 8 Gen 2 | SM8550 | V73 | ✅ Supported | +| Snapdragon 8 Gen 3 | SM8650 | V75 | ✅ Supported | +| Snapdragon 8s Gen 3 | SM8635 | V79 | ✅ Supported | +| Snapdragon 8 Elite | SM8750 | V81 | ✅ Supported | + +Other Snapdragon devices may work with MNN CPU/GPU fallback. + +## Setup + +### Option 1: From LocalDream APK (Recommended) + +Run the preparation script: + +```bash +./scripts/prepare_qnn_libs.sh +``` + +This will: +1. Download LocalDream APK (~54 MB) +2. Extract native libraries to `jniLibs/arm64-v8a/` +3. Extract QNN libraries to `assets/qnnlibs/` +4. Extract base models to `assets/cvtbase/` + +### Option 2: Manual Setup + +1. Download [LocalDream APK](https://github.com/xororz/local-dream/releases) +2. Extract the APK: + ```bash + unzip LocalDream_armv8a_*.apk -d extracted/ + ``` +3. Copy files: + ```bash + cp extracted/lib/arm64-v8a/libstable_diffusion_core.so \ + feature/qnn/src/main/jniLibs/arm64-v8a/ + + cp extracted/assets/qnnlibs/*.so \ + feature/qnn/src/main/assets/qnnlibs/ + + cp extracted/assets/cvtbase/* \ + feature/qnn/src/main/assets/cvtbase/ + ``` + +## Building + +The QNN module is only included in the `full` flavor: + +```bash +./gradlew :app:assembleFullDebug +``` + +## Usage + +### Starting the Backend + +```kotlin +// Inject LocalQnn +val localQnn: LocalQnn by inject() + +// Start the background service +localQnn.startService() + .subscribeOn(Schedulers.io()) + .subscribe() + +// Wait for service to be ready +localQnn.isAvailable() + .filter { it } + .firstOrError() + .subscribe { ready -> + // Backend is ready + } +``` + +### Generating Images + +```kotlin +val payload = TextToImagePayload( + prompt = "a beautiful sunset over mountains", + negativePrompt = "ugly, blurry", + width = 512, + height = 512, + samplingSteps = 20, + cfgScale = 7.0f, + seed = "-1", + // ... other parameters +) + +localQnn.processTextToImage(payload) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { bitmap -> + imageView.setImageBitmap(bitmap) + } +``` + +### Observing Progress + +```kotlin +localQnn.observeStatus() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { status -> + progressBar.progress = (status.step * 100 / status.maxStep) + } +``` + +## File Structure + +``` +feature/qnn/ +├── build.gradle.kts +├── src/main/ +│ ├── AndroidManifest.xml +│ ├── java/.../feature/qnn/ +│ │ ├── LocalQnnImpl.kt # Main implementation +│ │ ├── api/ +│ │ │ ├── QnnLocalApi.kt # Retrofit API interface +│ │ │ └── model/ApiModels.kt # Request/Response models +│ │ ├── di/QnnModule.kt # Koin DI module +│ │ ├── jni/QnnBridge.kt # JNI wrapper +│ │ ├── model/QnnModelManager.kt # Model management +│ │ └── service/ +│ │ ├── QnnBackendService.kt # Foreground service +│ │ └── QnnGenerationService.kt +│ ├── jniLibs/arm64-v8a/ +│ │ └── libstable_diffusion_core.so (NOT in git) +│ └── assets/ +│ ├── qnnlibs/*.so (NOT in git) +│ └── cvtbase/*.mnn (NOT in git) +``` + +## API Endpoints + +The native backend runs an HTTP server on `localhost:8081`: + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/health` | GET | Health check | +| `/txt2img` | POST | Text-to-image generation | +| `/img2img` | POST | Image-to-image generation | +| `/progress` | GET | Generation progress | +| `/interrupt` | POST | Cancel generation | +| `/models` | GET | List available models | +| `/load_model` | POST | Load a model | + +## Licensing + +- **Module code**: MIT (same as main project) +- **QNN SDK libraries**: Qualcomm proprietary license +- **libstable_diffusion_core.so**: MIT (from local-dream) +- **MNN Framework**: Apache 2.0 + +The QNN SDK libraries are **not** included in the source repository. They must be downloaded separately as described above. + +## Troubleshooting + +### Service doesn't start +- Check that all `.so` files are in place +- Verify device has ARM64 architecture +- Check logcat for native library errors + +### Generation is slow +- Ensure device supports QNN HTP +- Check that QNN libraries match your SoC version +- MNN CPU fallback is 10-50x slower than NPU + +### Out of memory +- Reduce image resolution +- Close other apps +- Some models require 8+ GB RAM + +## References + +- [local-dream](https://github.com/xororz/local-dream) - Original implementation +- [Qualcomm QNN SDK](https://www.qualcomm.com/developer/software/neural-processing-sdk) +- [MNN Framework](https://github.com/alibaba/MNN) diff --git a/feature/qnn/build.gradle.kts b/feature/qnn/build.gradle.kts new file mode 100644 index 000000000..e82fd16d9 --- /dev/null +++ b/feature/qnn/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + alias(libs.plugins.generic.library) + alias(libs.plugins.generic.flavors) +} + +android { + namespace = "dev.minios.pdaiv1.feature.qnn" + + defaultConfig { + ndk { + abiFilters += "arm64-v8a" + } + } + + sourceSets { + getByName("main") { + jniLibs.srcDirs("src/main/jniLibs") + } + } + + testOptions.unitTests.all { test -> + test.jvmArgs( + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED" + ) + } +} + +dependencies { + implementation(project(":core:common")) + implementation(project(":domain")) + implementation(libs.koin.core) + implementation(libs.koin.android) + implementation(libs.rx.kotlin) + implementation(libs.rx.java) + implementation(libs.retrofit.core) + implementation(libs.retrofit.converter.gson) + implementation(libs.okhttp.core) + implementation(libs.okhttp.logging) + implementation(libs.google.gson) + testImplementation(libs.test.junit) + testImplementation(libs.test.mockk) + testImplementation(libs.test.coroutines) +} diff --git a/feature/qnn/consumer-rules.pro b/feature/qnn/consumer-rules.pro new file mode 100644 index 000000000..ae8981ee3 --- /dev/null +++ b/feature/qnn/consumer-rules.pro @@ -0,0 +1,8 @@ +# Keep JNI methods +-keepclasseswithmembernames class * { + native ; +} + +# Keep QNN Bridge +-keep class dev.minios.pdaiv1.feature.qnn.jni.QnnBridge { *; } +-keep class dev.minios.pdaiv1.feature.qnn.jni.QnnBridge$* { *; } diff --git a/feature/qnn/proguard-rules.pro b/feature/qnn/proguard-rules.pro new file mode 100644 index 000000000..fb164d666 --- /dev/null +++ b/feature/qnn/proguard-rules.pro @@ -0,0 +1 @@ +# Add project specific ProGuard rules here. diff --git a/feature/qnn/src/main/AndroidManifest.xml b/feature/qnn/src/main/AndroidManifest.xml new file mode 100644 index 000000000..01868d5f4 --- /dev/null +++ b/feature/qnn/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/feature/qnn/src/main/assets/cvtbase/.gitkeep b/feature/qnn/src/main/assets/cvtbase/.gitkeep new file mode 100644 index 000000000..adbba57fa --- /dev/null +++ b/feature/qnn/src/main/assets/cvtbase/.gitkeep @@ -0,0 +1,9 @@ +# This directory should contain MNN base models: +# - clip_skip_1.mnn +# - clip_skip_2.mnn +# - tokenizer.json +# - unet.mnn +# - vae_decoder.mnn +# - vae_encoder.mnn +# +# Run scripts/prepare_qnn_libs.sh to download them from LocalDream APK. diff --git a/feature/qnn/src/main/assets/qnnlibs/.gitkeep b/feature/qnn/src/main/assets/qnnlibs/.gitkeep new file mode 100644 index 000000000..ebc4a46a0 --- /dev/null +++ b/feature/qnn/src/main/assets/qnnlibs/.gitkeep @@ -0,0 +1,12 @@ +# This directory should contain QNN libraries: +# - libQnnHtp.so +# - libQnnHtpV68.so, libQnnHtpV68Skel.so, libQnnHtpV68Stub.so +# - libQnnHtpV69.so, libQnnHtpV69Skel.so, libQnnHtpV69Stub.so +# - libQnnHtpV73.so, libQnnHtpV73Skel.so, libQnnHtpV73Stub.so +# - libQnnHtpV75.so, libQnnHtpV75Skel.so, libQnnHtpV75Stub.so +# - libQnnHtpV79.so, libQnnHtpV79Skel.so, libQnnHtpV79Stub.so +# - libQnnHtpV81.so, libQnnHtpV81Skel.so, libQnnHtpV81Stub.so +# - libQnnSystem.so +# +# These files are NOT committed to git due to Qualcomm proprietary licensing. +# Run scripts/prepare_qnn_libs.sh to download them from LocalDream APK. diff --git a/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/LocalQnnImpl.kt b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/LocalQnnImpl.kt new file mode 100644 index 000000000..9a0357787 --- /dev/null +++ b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/LocalQnnImpl.kt @@ -0,0 +1,758 @@ +package dev.minios.pdaiv1.feature.qnn + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.util.Base64 +import android.util.Log +import com.google.gson.Gson +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.qnn.LocalQnn +import dev.minios.pdaiv1.domain.feature.qnn.QnnGenerationResult +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.feature.qnn.api.model.CompleteEvent +import dev.minios.pdaiv1.feature.qnn.api.model.GenerateRequest +import dev.minios.pdaiv1.feature.qnn.api.model.ProgressEvent +import dev.minios.pdaiv1.feature.qnn.service.QnnBackendService +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import io.reactivex.rxjava3.subjects.PublishSubject +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.ByteArrayOutputStream +import java.io.File +import java.util.concurrent.TimeUnit +import kotlin.math.max +import kotlin.math.min + +/** + * Implementation of LocalQnn using QNN backend HTTP server with SSE streaming. + */ +internal class LocalQnnImpl( + private val context: Context, + private val httpClient: OkHttpClient, + private val gson: Gson, + private val preferenceManager: PreferenceManager, + private val fileProviderDescriptor: FileProviderDescriptor +) : LocalQnn { + + companion object { + private const val TAG = "LocalQnnImpl" + private const val BASE_URL = "http://127.0.0.1:8081" + private const val SERVER_STARTUP_TIMEOUT_MS = 30000L + private const val HEALTH_CHECK_INTERVAL_MS = 500L + } + + private val statusSubject: PublishSubject = PublishSubject.create() + + // Track current server state to restart if changed + @Volatile + private var currentServerWidth: Int = 0 + @Volatile + private var currentServerHeight: Int = 0 + @Volatile + private var currentServerModelId: String = "" + + override fun processTextToImage(payload: TextToImagePayload): Single { + // Check if Hires.Fix is enabled and we're on NPU + val useHires = payload.qnnHires.enabled && !preferenceManager.localQnnRunOnCpu + + return if (useHires) { + processTextToImageWithHires(payload) + } else { + processTextToImageDirect(payload) + } + } + + /** + * Direct text-to-image generation without Hires.Fix. + */ + private fun processTextToImageDirect(payload: TextToImagePayload): Single { + return ensureServerRunning(payload.width, payload.height) + .andThen(Single.create { emitter -> + try { + val scheduler = mapSamplerToQnnScheduler(payload.sampler) + val request = GenerateRequest( + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + steps = payload.samplingSteps, + cfg = payload.cfgScale, + seed = parseSeed(payload.seed), + scheduler = scheduler, + useOpencl = preferenceManager.localQnnUseOpenCL, + showDiffusionProcess = preferenceManager.localQnnShowDiffusionProcess, + showDiffusionStride = 1 + ) + + val result = executeGenerateRequest(request) + emitter.onSuccess(result) + } catch (e: Exception) { + Log.e(TAG, "Text-to-image error", e) + emitter.onError(e) + } + }) + .subscribeOn(Schedulers.io()) + } + + /** + * Text-to-image with Hires.Fix (NPU only): + * 1. Generate at base resolution (from payload width/height) + * 2. Upscale to target resolution (with same aspect ratio) + * 3. Run img2img refinement pass at target resolution + * + * Supported upscale paths: + * - 512×512 → 768×768, 1024×1024 + * - 512×768 → 768×1024 + * - 768×512 → 1024×768 + * - 768×768 → 1024×1024 + */ + private fun processTextToImageWithHires(payload: TextToImagePayload): Single { + // Base resolution is from payload (user selected) + val baseWidth = payload.width + val baseHeight = payload.height + val targetWidth = payload.qnnHires.targetWidth + val targetHeight = payload.qnnHires.targetHeight + val hiresSteps = if (payload.qnnHires.steps > 0) payload.qnnHires.steps else payload.samplingSteps + val hiresDenoise = payload.qnnHires.denoisingStrength + + Log.i(TAG, "Hires.Fix: ${baseWidth}x${baseHeight} → ${targetWidth}x${targetHeight}, " + + "steps=$hiresSteps, denoise=$hiresDenoise") + + // Step 1: Generate at base resolution + return ensureServerRunning(baseWidth, baseHeight) + .andThen(Single.create { emitter -> + try { + val scheduler = mapSamplerToQnnScheduler(payload.sampler) + val request = GenerateRequest( + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = baseWidth, + height = baseHeight, + steps = payload.samplingSteps, + cfg = payload.cfgScale, + seed = parseSeed(payload.seed), + scheduler = scheduler, + useOpencl = false, // NPU mode + showDiffusionProcess = preferenceManager.localQnnShowDiffusionProcess, + showDiffusionStride = 1 + ) + + Log.d(TAG, "Hires Step 1: Generating base image at ${baseWidth}x${baseHeight}") + val baseResult = executeGenerateRequest(request) + emitter.onSuccess(baseResult) + } catch (e: Exception) { + Log.e(TAG, "Hires Step 1 error", e) + emitter.onError(e) + } + }) + .subscribeOn(Schedulers.io()) + // Step 2: Upscale and refine + .flatMap { baseResult -> + Log.d(TAG, "Hires Step 2: Upscaling ${baseWidth}x${baseHeight} → ${targetWidth}x${targetHeight}") + + // Upscale bitmap to target resolution + val upscaledBitmap = Bitmap.createScaledBitmap( + baseResult.bitmap, targetWidth, targetHeight, true + ) + val upscaledBase64 = bitmapToBase64NoWrap(upscaledBitmap) + + // Restart server at target resolution + ensureServerRunning(targetWidth, targetHeight) + .andThen(Single.create { emitter -> + try { + val scheduler = mapSamplerToQnnScheduler(payload.sampler) + val request = GenerateRequest( + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = targetWidth, + height = targetHeight, + steps = hiresSteps, + cfg = payload.cfgScale, + seed = baseResult.seed, // Use same seed for consistency + scheduler = scheduler, + useOpencl = false, // NPU mode + showDiffusionProcess = preferenceManager.localQnnShowDiffusionProcess, + showDiffusionStride = 1, + image = upscaledBase64, + denoiseStrength = hiresDenoise + ) + + Log.d(TAG, "Hires Step 3: Refining at ${targetWidth}x${targetHeight}") + val refinedResult = executeGenerateRequest(request) + emitter.onSuccess(refinedResult) + } catch (e: Exception) { + Log.e(TAG, "Hires Step 3 error", e) + emitter.onError(e) + } + }) + } + } + + override fun processImageToImage(payload: ImageToImagePayload): Single { + // Check if we need to use "Only masked" mode + val hasMask = payload.base64MaskImage.isNotEmpty() + val useOnlyMasked = hasMask && payload.inPaintFullRes + + return if (useOnlyMasked) { + processImageToImageOnlyMasked(payload) + } else { + processImageToImageWholePicture(payload) + } + } + + /** + * Process img2img with "Whole picture" mode - sends full image to QNN. + */ + private fun processImageToImageWholePicture(payload: ImageToImagePayload): Single { + return ensureServerRunning(payload.width, payload.height) + .andThen(Single.create { emitter -> + try { + val scheduler = mapSamplerToQnnScheduler(payload.sampler) + // Remove line breaks from base64 (Android Base64.DEFAULT adds them) + val cleanImage = payload.base64Image.replace("\n", "").replace("\r", "") + val cleanMask = payload.base64MaskImage.replace("\n", "").replace("\r", "") + val request = GenerateRequest( + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + steps = payload.samplingSteps, + cfg = payload.cfgScale, + seed = parseSeed(payload.seed), + scheduler = scheduler, + useOpencl = preferenceManager.localQnnUseOpenCL, + showDiffusionProcess = preferenceManager.localQnnShowDiffusionProcess, + showDiffusionStride = 1, + image = cleanImage, + mask = cleanMask.takeIf { it.isNotEmpty() }, + denoiseStrength = payload.denoisingStrength + ) + + val result = executeGenerateRequest(request) + emitter.onSuccess(result) + } catch (e: Exception) { + Log.e(TAG, "Image-to-image error", e) + emitter.onError(e) + } + }) + .subscribeOn(Schedulers.io()) + } + + /** + * Process img2img with "Only masked" mode: + * 1. Find bounding box of mask + * 2. Crop masked area with padding + * 3. Scale to target resolution + * 4. Generate with QNN + * 5. Scale result back + * 6. Composite onto original image + */ + private fun processImageToImageOnlyMasked(payload: ImageToImagePayload): Single { + return Single.fromCallable { + // Decode original image and mask + val originalBitmap = base64ToBitmap(payload.base64Image) + val maskBitmap = base64ToBitmap(payload.base64MaskImage) + + // Find bounding box of the mask (white pixels) + val maskBounds = findMaskBounds(maskBitmap, payload.inPaintFullResPadding) + + // Crop the region from original image + val croppedImage = Bitmap.createBitmap( + originalBitmap, + maskBounds.left, maskBounds.top, + maskBounds.width(), maskBounds.height() + ) + + // Crop corresponding mask region + val croppedMask = Bitmap.createBitmap( + maskBitmap, + maskBounds.left, maskBounds.top, + maskBounds.width(), maskBounds.height() + ) + + // Scale to target resolution + val scaledImage = Bitmap.createScaledBitmap(croppedImage, payload.width, payload.height, true) + val scaledMask = Bitmap.createScaledBitmap(croppedMask, payload.width, payload.height, true) + + InpaintOnlyMaskedData( + originalBitmap = originalBitmap, + maskBitmap = maskBitmap, + scaledImage = scaledImage, + scaledMask = scaledMask, + maskBounds = maskBounds + ) + } + .subscribeOn(Schedulers.io()) + .flatMap { data -> + // Run generation on the cropped/scaled region + ensureServerRunning(payload.width, payload.height) + .andThen(Single.create { emitter -> + try { + val scheduler = mapSamplerToQnnScheduler(payload.sampler) + val cleanImage = bitmapToBase64NoWrap(data.scaledImage) + val cleanMask = bitmapToBase64NoWrap(data.scaledMask) + + val request = GenerateRequest( + prompt = payload.prompt, + negativePrompt = payload.negativePrompt, + width = payload.width, + height = payload.height, + steps = payload.samplingSteps, + cfg = payload.cfgScale, + seed = parseSeed(payload.seed), + scheduler = scheduler, + useOpencl = preferenceManager.localQnnUseOpenCL, + showDiffusionProcess = preferenceManager.localQnnShowDiffusionProcess, + showDiffusionStride = 1, + image = cleanImage, + mask = cleanMask, + denoiseStrength = payload.denoisingStrength + ) + + val genResult = executeGenerateRequest(request) + + // Scale generated result back to crop size + val scaledBack = Bitmap.createScaledBitmap( + genResult.bitmap, + data.maskBounds.width(), + data.maskBounds.height(), + true + ) + + // Composite onto original image + val compositedBitmap = compositeWithMask( + data.originalBitmap, + scaledBack, + data.maskBitmap, + data.maskBounds + ) + + emitter.onSuccess(QnnGenerationResult( + bitmap = compositedBitmap, + seed = genResult.seed, + width = data.originalBitmap.width, + height = data.originalBitmap.height, + )) + } catch (e: Exception) { + Log.e(TAG, "Image-to-image (only masked) error", e) + emitter.onError(e) + } + }) + } + } + + private data class InpaintOnlyMaskedData( + val originalBitmap: Bitmap, + val maskBitmap: Bitmap, + val scaledImage: Bitmap, + val scaledMask: Bitmap, + val maskBounds: Rect + ) + + /** + * Find the bounding box of white pixels in mask, with padding. + */ + private fun findMaskBounds(mask: Bitmap, padding: Int): Rect { + var minX = mask.width + var minY = mask.height + var maxX = 0 + var maxY = 0 + + for (y in 0 until mask.height) { + for (x in 0 until mask.width) { + val pixel = mask.getPixel(x, y) + // Check if pixel is "white" (masked area) - check red channel > 128 + if (Color.red(pixel) > 128) { + minX = min(minX, x) + minY = min(minY, y) + maxX = max(maxX, x) + maxY = max(maxY, y) + } + } + } + + // If no mask found, use whole image + if (minX > maxX || minY > maxY) { + return Rect(0, 0, mask.width, mask.height) + } + + // Add padding + minX = max(0, minX - padding) + minY = max(0, minY - padding) + maxX = min(mask.width, maxX + padding) + maxY = min(mask.height, maxY + padding) + + return Rect(minX, minY, maxX, maxY) + } + + /** + * Composite generated image onto original using mask. + */ + private fun compositeWithMask( + original: Bitmap, + generated: Bitmap, + mask: Bitmap, + bounds: Rect + ): Bitmap { + val result = original.copy(Bitmap.Config.ARGB_8888, true) + val canvas = Canvas(result) + + // Draw the generated image at the mask bounds + canvas.drawBitmap(generated, null, bounds, null) + + return result + } + + private fun base64ToBitmap(base64: String): Bitmap { + val clean = base64.replace("\n", "").replace("\r", "") + val bytes = Base64.decode(clean, Base64.DEFAULT) + return BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + } + + private fun bitmapToBase64NoWrap(bitmap: Bitmap): String { + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP) + } + + /** + * Ensure the QNN backend server is running with correct resolution and model. + * If running with different resolution or model, restarts the server. + */ + private fun ensureServerRunning(width: Int, height: Int): Completable { + val requestedModelId = preferenceManager.localQnnModelId + return isAvailable() + .flatMapCompletable { isRunning -> + val resolutionChanged = isRunning && + (currentServerWidth != width || currentServerHeight != height) + val modelChanged = isRunning && currentServerModelId != requestedModelId + + when { + !isRunning -> { + Log.d(TAG, "Server not running, starting with ${width}x${height}...") + startServerAndWait(width, height) + } + resolutionChanged -> { + Log.d(TAG, "Resolution changed from ${currentServerWidth}x${currentServerHeight} to ${width}x${height}, restarting server...") + stopService() + .andThen(Completable.timer(1000, java.util.concurrent.TimeUnit.MILLISECONDS)) + .andThen(startServerAndWait(width, height)) + } + modelChanged -> { + Log.d(TAG, "Model changed from $currentServerModelId to $requestedModelId, restarting server...") + stopService() + .andThen(Completable.timer(1000, java.util.concurrent.TimeUnit.MILLISECONDS)) + .andThen(startServerAndWait(width, height)) + } + else -> { + Log.d(TAG, "Server already running with ${width}x${height}") + Completable.complete() + } + } + } + } + + private fun startServerAndWait(width: Int, height: Int): Completable { + return Completable.create { emitter -> + try { + val modelPath = getModelPath() + val modelId = preferenceManager.localQnnModelId + val runOnCpu = preferenceManager.localQnnRunOnCpu + Log.d(TAG, "Starting server with model: $modelPath, resolution: ${width}x${height}, runOnCpu: $runOnCpu") + + val intent = QnnBackendService.createStartIntent(context, modelPath, width, height, runOnCpu) + context.startForegroundService(intent) + + // Wait for server to be ready + val startTime = System.currentTimeMillis() + while (System.currentTimeMillis() - startTime < SERVER_STARTUP_TIMEOUT_MS) { + if (checkServerHealth()) { + Log.d(TAG, "Server is ready with resolution ${width}x${height}") + currentServerWidth = width + currentServerHeight = height + currentServerModelId = modelId + emitter.onComplete() + return@create + } + Thread.sleep(HEALTH_CHECK_INTERVAL_MS) + } + + emitter.onError(RuntimeException("Server startup timeout")) + } catch (e: Exception) { + Log.e(TAG, "Failed to start server", e) + emitter.onError(e) + } + }.subscribeOn(Schedulers.io()) + } + + private fun getModelPath(): String { + val modelId = preferenceManager.localQnnModelId + return when { + // Old single custom model (legacy support) + modelId == LocalAiModel.CustomQnn.id -> { + preferenceManager.localQnnCustomModelPath + } + // Scanned custom model - ID format is "CUSTOM_QNN:modelName" + modelId.startsWith("CUSTOM_QNN:") -> { + val modelName = modelId.removePrefix("CUSTOM_QNN:") + File(preferenceManager.localQnnCustomModelPath, modelName).absolutePath + } + // Downloaded model path + else -> { + File(fileProviderDescriptor.localModelDirPath, modelId).absolutePath + } + } + } + + private fun checkServerHealth(): Boolean { + return try { + val request = Request.Builder() + .url("$BASE_URL/health") + .get() + .build() + + val response = httpClient.newBuilder() + .connectTimeout(2, TimeUnit.SECONDS) + .readTimeout(2, TimeUnit.SECONDS) + .build() + .newCall(request) + .execute() + + val isOk = response.isSuccessful + response.close() + isOk + } catch (e: Exception) { + false + } + } + + /** + * Execute generation request using SSE streaming. + * The native server returns Server-Sent Events with progress and final image. + */ + private fun executeGenerateRequest(generateRequest: GenerateRequest): QnnGenerationResult { + val jsonBody = gson.toJson(generateRequest) + Log.d(TAG, "Generate request: $jsonBody") + + val request = Request.Builder() + .url("$BASE_URL/generate") + .post(jsonBody.toRequestBody("application/json".toMediaType())) + .header("Accept", "text/event-stream") + .build() + + // Use longer timeout for generation + val client = httpClient.newBuilder() + .readTimeout(10, TimeUnit.MINUTES) + .build() + + val response = client.newCall(request).execute() + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "No error body" + Log.e(TAG, "Generate request failed with code ${response.code}: $errorBody") + throw RuntimeException("Generate request failed: ${response.code} - $errorBody") + } + + val body = response.body ?: throw RuntimeException("Empty response body") + val source = body.source() + var result: QnnGenerationResult? = null + var eventType: String? = null + + // Read SSE events line by line, blocking until data is available + try { + while (true) { + // readUtf8Line() blocks until a line is available or stream ends + val line = source.readUtf8Line() ?: break + + when { + line.startsWith("event: ") -> { + eventType = line.removePrefix("event: ").trim() + } + line.startsWith("data: ") -> { + val eventData = line.removePrefix("data: ").trim() + + when (eventType) { + "progress" -> { + try { + val progress = gson.fromJson(eventData, ProgressEvent::class.java) + val previewBitmap = progress.image?.takeIf { it.isNotEmpty() }?.let { base64 -> + try { + // Preview images are typically at generation resolution + decodeBase64Image(base64, generateRequest.width, generateRequest.height) + } catch (e: Exception) { + Log.w(TAG, "Failed to decode preview", e) + null + } + } + statusSubject.onNext( + LocalDiffusionStatus( + current = progress.step, + total = progress.totalSteps, + previewBitmap = previewBitmap + ) + ) + Log.d(TAG, "Progress: ${progress.step}/${progress.totalSteps}" + + if (previewBitmap != null) " (with preview)" else "") + } catch (e: Exception) { + Log.w(TAG, "Failed to parse progress", e) + } + } + "complete" -> { + try { + val complete = gson.fromJson(eventData, CompleteEvent::class.java) + val bitmap = decodeBase64Image(complete.image, complete.width, complete.height) + result = QnnGenerationResult( + bitmap = bitmap, + seed = complete.seed, + width = complete.width, + height = complete.height, + ) + Log.i(TAG, "Generation complete: ${complete.width}x${complete.height}, " + + "time=${complete.generationTimeMs}ms, seed=${complete.seed}") + // Exit loop after receiving complete event + break + } catch (e: Exception) { + Log.e(TAG, "Failed to parse complete event", e) + throw RuntimeException("Failed to decode result image", e) + } + } + "error" -> { + Log.e(TAG, "Server error: $eventData") + throw RuntimeException("Generation error: $eventData") + } + } + } + } + } + } finally { + response.close() + } + + return result ?: throw RuntimeException("No image received from server") + } + + override fun interrupt(): Completable { + // Native server doesn't have interrupt endpoint yet + return Completable.complete() + } + + override fun observeStatus(): Observable { + return statusSubject + } + + override fun isAvailable(): Single { + return Single.fromCallable { + try { + val request = Request.Builder() + .url("$BASE_URL/health") + .get() + .build() + + val response = httpClient.newCall(request).execute() + val isOk = response.isSuccessful + response.close() + isOk + } catch (e: Exception) { + Log.d(TAG, "Health check failed: ${e.message}") + false + } + }.subscribeOn(Schedulers.io()) + } + + override fun startService(): Completable { + return Completable.fromAction { + // Note: Model path should be provided from preferences + // This is a placeholder that needs to be called with proper model path + Log.w(TAG, "startService called without model path - use startServiceWithModel instead") + } + } + + fun startServiceWithModel(modelPath: String, width: Int = 512, height: Int = 512): Completable { + return Completable.fromAction { + val intent = QnnBackendService.createStartIntent(context, modelPath, width, height) + context.startForegroundService(intent) + } + } + + override fun stopService(): Completable { + return Completable.fromAction { + currentServerWidth = 0 + currentServerHeight = 0 + val intent = QnnBackendService.createStopIntent(context) + context.startService(intent) + } + } + + /** + * Parse seed string to Long. + * Returns null for random seed (empty string, -1, or invalid). + * Server will generate random seed when null/omitted. + */ + private fun parseSeed(seed: String): Long? { + if (seed.isBlank()) return null + return try { + val value = seed.toLong() + if (value < 0) null else value + } catch (e: NumberFormatException) { + null + } + } + + /** + * Map A1111-style sampler names to QNN scheduler types. + * QNN backend supports: "dpm" (DPM++ 2M), "euler_a" (Euler Ancestral) + */ + private fun mapSamplerToQnnScheduler(sampler: String): String { + return when { + sampler.lowercase().contains("euler") -> "euler_a" + sampler.lowercase().contains("dpm") -> "dpm" + sampler.lowercase().contains("ddim") -> "dpm" // Fallback + else -> preferenceManager.localQnnScheduler.ifEmpty { "dpm" } + } + } + + private fun decodeBase64Image(base64: String, width: Int, height: Int): Bitmap { + val imageData = Base64.decode(base64, Base64.DEFAULT) + + // First try standard image formats (PNG, JPEG) + val standardBitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.size) + if (standardBitmap != null) { + return standardBitmap + } + + // Server sends raw RGB data (width * height * 3 bytes) + val expectedSize = width * height * 3 + if (imageData.size != expectedSize) { + throw IllegalStateException( + "Failed to decode image. Size mismatch: got ${imageData.size}, expected $expectedSize for ${width}x${height}x3" + ) + } + + // Convert RGB bytes to ARGB_8888 Bitmap + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val pixels = IntArray(width * height) + + for (i in 0 until width * height) { + val r = imageData[i * 3].toInt() and 0xFF + val g = imageData[i * 3 + 1].toInt() and 0xFF + val b = imageData[i * 3 + 2].toInt() and 0xFF + pixels[i] = (0xFF shl 24) or (r shl 16) or (g shl 8) or b + } + + bitmap.setPixels(pixels, 0, width, 0, 0, width, height) + return bitmap + } +} diff --git a/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/api/model/ApiModels.kt b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/api/model/ApiModels.kt new file mode 100644 index 000000000..923017e97 --- /dev/null +++ b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/api/model/ApiModels.kt @@ -0,0 +1,113 @@ +package dev.minios.pdaiv1.feature.qnn.api.model + +import com.google.gson.annotations.SerializedName + +/** + * Request for /generate endpoint. + * Supports both txt2img and img2img (when image field is provided). + * + * Based on local-dream native server implementation. + */ +data class GenerateRequest( + @SerializedName("prompt") + val prompt: String, + + @SerializedName("negative_prompt") + val negativePrompt: String = "", + + @SerializedName("width") + val width: Int = 512, + + @SerializedName("height") + val height: Int = 512, + + @SerializedName("steps") + val steps: Int = 20, + + @SerializedName("cfg") + val cfg: Float = 7.0f, + + // Seed for random generation. Use null for random seed (server will generate) + @SerializedName("seed") + val seed: Long? = null, + + @SerializedName("scheduler") + val scheduler: String = "dpm", + + @SerializedName("use_opencl") + val useOpencl: Boolean = false, + + @SerializedName("show_diffusion_process") + val showDiffusionProcess: Boolean = false, + + @SerializedName("show_diffusion_stride") + val showDiffusionStride: Int = 1, + + @SerializedName("denoise_strength") + val denoiseStrength: Float = 0.6f, + + // For img2img - base64 encoded image + @SerializedName("image") + val image: String? = null, + + // For inpainting - base64 encoded mask + @SerializedName("mask") + val mask: String? = null +) + +/** + * SSE event data for progress updates. + */ +data class ProgressEvent( + @SerializedName("type") + val type: String, // "progress", "complete", "error" + + @SerializedName("step") + val step: Int = 0, + + @SerializedName("total_steps") + val totalSteps: Int = 0, + + @SerializedName("image") + val image: String? = null // Base64 encoded - preview or final image +) + +/** + * SSE event data for completed generation. + */ +data class CompleteEvent( + @SerializedName("type") + val type: String = "complete", + + @SerializedName("image") + val image: String, // Base64 encoded PNG + + @SerializedName("seed") + val seed: Long, + + @SerializedName("width") + val width: Int, + + @SerializedName("height") + val height: Int, + + @SerializedName("channels") + val channels: Int = 3, + + @SerializedName("generation_time_ms") + val generationTimeMs: Long, + + @SerializedName("first_step_time_ms") + val firstStepTimeMs: Long +) + +/** + * SSE event data for errors. + */ +data class ErrorEvent( + @SerializedName("type") + val type: String = "error", + + @SerializedName("message") + val message: String +) diff --git a/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/di/QnnModule.kt b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/di/QnnModule.kt new file mode 100644 index 000000000..e0064213f --- /dev/null +++ b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/di/QnnModule.kt @@ -0,0 +1,48 @@ +package dev.minios.pdaiv1.feature.qnn.di + +import com.google.gson.Gson +import dev.minios.pdaiv1.domain.feature.qnn.LocalQnn +import dev.minios.pdaiv1.feature.qnn.LocalQnnImpl +import dev.minios.pdaiv1.feature.qnn.model.QnnModelManager +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.koin.android.ext.koin.androidContext +import org.koin.core.qualifier.named +import org.koin.dsl.module +import java.util.concurrent.TimeUnit + +private const val QNN_QUALIFIER = "qnn" + +val qnnModule = module { + + // OkHttp client for QNN local API (used for SSE streaming) + single(named(QNN_QUALIFIER)) { + OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.MINUTES) // Very long timeout for generation + .writeTimeout(60, TimeUnit.SECONDS) + .addInterceptor( + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BASIC + } + ) + .build() + } + + // Gson for JSON parsing + single { Gson() } + + // Model manager + single { QnnModelManager(androidContext()) } + + // LocalQnn implementation using SSE streaming + single { + LocalQnnImpl( + context = androidContext(), + httpClient = get(named(QNN_QUALIFIER)), + gson = get(), + preferenceManager = get(), + fileProviderDescriptor = get() + ) + } +} diff --git a/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/model/QnnModelManager.kt b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/model/QnnModelManager.kt new file mode 100644 index 000000000..5298c95ed --- /dev/null +++ b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/model/QnnModelManager.kt @@ -0,0 +1,174 @@ +package dev.minios.pdaiv1.feature.qnn.model + +import android.content.Context +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream + +/** + * Manages QNN libraries and models. + * + * Responsibilities: + * - Copy QNN .so files from assets to app's private directory + * - Copy base MNN models from assets + * - Manage downloaded SD models + * - Track model conversion state + */ +class QnnModelManager( + private val context: Context +) { + + private val filesDir = context.filesDir + private val qnnLibsDir = File(filesDir, "qnn_libs") + private val modelsDir = File(filesDir, "qnn_models") + private val cvtbaseDir = File(filesDir, "cvtbase") + + /** + * Prepare QNN libraries by copying from assets to private directory. + * Libraries need to be in a directory accessible at runtime. + */ + suspend fun prepareLibraries(): Result = withContext(Dispatchers.IO) { + runCatching { + qnnLibsDir.mkdirs() + cvtbaseDir.mkdirs() + modelsDir.mkdirs() + + // Copy QNN libraries + copyAssetsDirectory("qnnlibs", qnnLibsDir) + + // Copy base models + copyAssetsDirectory("cvtbase", cvtbaseDir) + } + } + + /** + * Check if QNN libraries are prepared. + */ + fun areLibrariesReady(): Boolean { + val requiredLibs = listOf("libQnnHtp.so", "libQnnSystem.so") + return requiredLibs.all { File(qnnLibsDir, it).exists() } + } + + /** + * Get path to QNN libraries directory. + */ + fun getQnnLibsPath(): String = qnnLibsDir.absolutePath + + /** + * Get path to base models directory. + */ + fun getModelsPath(): String = cvtbaseDir.absolutePath + + /** + * Get path to downloaded models directory. + */ + fun getDownloadedModelsPath(): String = modelsDir.absolutePath + + /** + * Get list of available models. + */ + fun getAvailableModels(): List { + return modelsDir.listFiles()?.filter { it.isDirectory }?.map { dir -> + QnnModel( + id = dir.name, + name = dir.name, + path = dir.absolutePath, + isConverted = File(dir, "unet.qnn").exists(), + sizeBytes = dir.walkTopDown().filter { it.isFile }.sumOf { it.length() } + ) + } ?: emptyList() + } + + /** + * Check if device supports QNN HTP (NPU). + */ + fun checkQnnSupport(): QnnSupportStatus { + // Check for Qualcomm SoC + val socModel = try { + File("/sys/devices/soc0/soc_id").readText().trim() + } catch (e: Exception) { + null + } + + val supportedSocs = mapOf( + "457" to "SM8450", // Snapdragon 8 Gen 1 + "530" to "SM8475", // Snapdragon 8+ Gen 1 + "536" to "SM8550", // Snapdragon 8 Gen 2 + "557" to "SM8650", // Snapdragon 8 Gen 3 + "614" to "SM8750" // Snapdragon 8 Elite + ) + + val socName = supportedSocs[socModel] + return if (socName != null) { + QnnSupportStatus.Supported(socName) + } else { + QnnSupportStatus.Unsupported( + "Device SoC not supported. QNN HTP requires Snapdragon 8 Gen 1 or newer." + ) + } + } + + /** + * Delete a model. + */ + suspend fun deleteModel(modelId: String): Result = withContext(Dispatchers.IO) { + runCatching { + val modelDir = File(modelsDir, modelId) + if (modelDir.exists()) { + modelDir.deleteRecursively() + } + } + } + + private fun copyAssetsDirectory(assetPath: String, targetDir: File) { + val assetManager = context.assets + val files = assetManager.list(assetPath) ?: return + + for (filename in files) { + if (filename == ".gitkeep") continue + + val assetFilePath = "$assetPath/$filename" + val targetFile = File(targetDir, filename) + + try { + // Check if it's a directory + val subFiles = assetManager.list(assetFilePath) + if (subFiles != null && subFiles.isNotEmpty()) { + targetFile.mkdirs() + copyAssetsDirectory(assetFilePath, targetFile) + } else { + // It's a file + if (!targetFile.exists() || targetFile.length() == 0L) { + assetManager.open(assetFilePath).use { input -> + FileOutputStream(targetFile).use { output -> + input.copyTo(output) + } + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } +} + +/** + * Represents a Stable Diffusion model for QNN. + */ +data class QnnModel( + val id: String, + val name: String, + val path: String, + val isConverted: Boolean, + val sizeBytes: Long +) + +/** + * QNN support status for the device. + */ +sealed class QnnSupportStatus { + data class Supported(val socName: String) : QnnSupportStatus() + data class Unsupported(val reason: String) : QnnSupportStatus() +} diff --git a/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/service/QnnBackendService.kt b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/service/QnnBackendService.kt new file mode 100644 index 000000000..9e627ebfe --- /dev/null +++ b/feature/qnn/src/main/java/dev/minios/pdaiv1/feature/qnn/service/QnnBackendService.kt @@ -0,0 +1,562 @@ +package dev.minios.pdaiv1.feature.qnn.service + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.IBinder +import android.util.Log +import androidx.core.app.NotificationCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.util.concurrent.TimeUnit +import java.util.zip.ZipFile + +/** + * Foreground service that manages the QNN backend server process. + * + * The service: + * 1. Copies QNN libraries from assets to private directory + * 2. Starts native executable (libstable_diffusion_core.so) as a process + * 3. Monitors process output and health + * 4. Keeps running until explicitly stopped + * + * Based on local-dream's BackendService implementation. + */ +class QnnBackendService : Service() { + + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private var backendProcess: Process? = null + private lateinit var runtimeDir: File + + companion object { + private const val TAG = "QnnBackendService" + private const val NOTIFICATION_CHANNEL_ID = "qnn_backend_channel" + private const val NOTIFICATION_ID = 9081 + private const val EXECUTABLE_NAME = "libstable_diffusion_core.so" + private const val RUNTIME_DIR = "qnn_runtime" + private const val SERVER_PORT = 8081 + + const val ACTION_START = "dev.minios.pdaiv1.feature.qnn.START" + const val ACTION_STOP = "dev.minios.pdaiv1.feature.qnn.STOP" + const val EXTRA_MODEL_PATH = "model_path" + const val EXTRA_WIDTH = "width" + const val EXTRA_HEIGHT = "height" + const val EXTRA_RUN_ON_CPU = "run_on_cpu" + + private object StateHolder { + val _backendState = MutableStateFlow(BackendState.Idle) + } + + val backendState: StateFlow = StateHolder._backendState + + private fun updateState(state: BackendState) { + StateHolder._backendState.value = state + } + + fun createStartIntent( + context: Context, + modelPath: String, + width: Int = 512, + height: Int = 512, + runOnCpu: Boolean = false + ): Intent { + return Intent(context, QnnBackendService::class.java).apply { + action = ACTION_START + putExtra(EXTRA_MODEL_PATH, modelPath) + putExtra(EXTRA_WIDTH, width) + putExtra(EXTRA_HEIGHT, height) + putExtra(EXTRA_RUN_ON_CPU, runOnCpu) + } + } + + fun createStopIntent(context: Context): Intent { + return Intent(context, QnnBackendService::class.java).apply { + action = ACTION_STOP + } + } + } + + sealed class BackendState { + object Idle : BackendState() + object Starting : BackendState() + object Running : BackendState() + data class Error(val message: String) : BackendState() + } + + override fun onCreate() { + super.onCreate() + createNotificationChannel() + prepareRuntimeDir() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.i(TAG, "Service started command: ${intent?.action}") + startForeground(NOTIFICATION_ID, createNotification("Initializing...")) + + when (intent?.action) { + ACTION_START -> { + val modelPath = intent.getStringExtra(EXTRA_MODEL_PATH) + val width = intent.getIntExtra(EXTRA_WIDTH, 512) + val height = intent.getIntExtra(EXTRA_HEIGHT, 512) + val runOnCpu = intent.getBooleanExtra(EXTRA_RUN_ON_CPU, false) + if (modelPath != null) { + startBackend(modelPath, width, height, runOnCpu) + } else { + updateState(BackendState.Error("Model path not provided")) + stopSelf() + } + } + ACTION_STOP -> { + stopBackend() + stopForeground(STOP_FOREGROUND_REMOVE) + stopSelf() + return START_NOT_STICKY + } + } + + return START_NOT_STICKY + } + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onDestroy() { + stopBackend() + serviceScope.cancel() + super.onDestroy() + } + + private fun prepareRuntimeDir() { + try { + runtimeDir = File(filesDir, RUNTIME_DIR).apply { + if (!exists()) mkdirs() + } + + // Copy QNN libraries from assets + val qnnLibsAssets = assets.list("qnnlibs") + qnnLibsAssets?.forEach { fileName -> + if (fileName == ".gitkeep") return@forEach + val targetLib = File(runtimeDir, fileName) + + val needsCopy = !targetLib.exists() || run { + val assetInputStream = assets.open("qnnlibs/$fileName") + val assetSize = assetInputStream.use { it.available().toLong() } + targetLib.length() != assetSize + } + + if (needsCopy) { + assets.open("qnnlibs/$fileName").use { input -> + FileOutputStream(targetLib).use { output -> + input.copyTo(output) + } + } + Log.d(TAG, "Copied $fileName to runtime directory") + } + + targetLib.setReadable(true, true) + targetLib.setExecutable(true, true) + } + + runtimeDir.setReadable(true, true) + runtimeDir.setExecutable(true, true) + + Log.i(TAG, "Runtime directory prepared: ${runtimeDir.absolutePath}") + Log.i(TAG, "Runtime files: ${runtimeDir.list()?.joinToString()}") + + } catch (e: Exception) { + Log.e(TAG, "Prepare runtime dir failed", e) + updateState(BackendState.Error("Prepare runtime dir failed: ${e.message}")) + throw RuntimeException("Failed to prepare runtime directory", e) + } + } + + private fun startBackend(modelPath: String, width: Int, height: Int, runOnCpu: Boolean) { + Log.i(TAG, "Backend start, model: $modelPath, resolution: ${width}×${height}, CPU mode: $runOnCpu") + updateState(BackendState.Starting) + updateNotification(if (runOnCpu) "Starting CPU backend..." else "Starting NPU backend...") + + serviceScope.launch { + try { + val nativeDir = applicationInfo.nativeLibraryDir + val modelsDir = findModelDirectory(File(modelPath)) + val executableFile = File(nativeDir, EXECUTABLE_NAME) + + if (modelsDir == null) { + Log.e(TAG, "Model directory not found: $modelPath") + updateState(BackendState.Error("Model not found")) + updateNotification("Error: Model not found") + return@launch + } + + Log.i(TAG, "Using model directory: ${modelsDir.absolutePath}") + + // Auto-detect CPU/NPU mode based on model files + // If model has unet.bin -> NPU mode, if only unet.mnn -> CPU mode + val hasNpuModel = File(modelsDir, "unet.bin").exists() + val hasMnnModel = File(modelsDir, "unet.mnn").exists() + val actualRunOnCpu = if (hasNpuModel) runOnCpu else hasMnnModel + + if (actualRunOnCpu != runOnCpu) { + Log.i(TAG, "Auto-switching to ${if (actualRunOnCpu) "CPU" else "NPU"} mode based on model files") + } + + if (!executableFile.exists()) { + Log.e(TAG, "Executable not found: ${executableFile.absolutePath}") + updateState(BackendState.Error("Executable not found")) + updateNotification("Error: Executable not found") + return@launch + } + + val command: MutableList + + if (actualRunOnCpu) { + // CPU mode - use MNN models + // Binary expects clip.mnn path and auto-upgrades to clip_v2.mnn if found + // IMPORTANT: We must pass clip.mnn path, not clip_v2.mnn, because the binary + // only sets use_clip_v2=true when it detects clip.mnn path and finds clip_v2.mnn + val clipMnnFile = File(modelsDir, "clip.mnn") + val clipV2MnnFile = File(modelsDir, "clip_v2.mnn") + val unetMnnFile = File(modelsDir, "unet.mnn") + val vaeDecoderMnnFile = File(modelsDir, "vae_decoder.mnn") + val tokenizerFile = File(modelsDir, "tokenizer.json") + + val hasClip = clipMnnFile.exists() || clipV2MnnFile.exists() + if (!hasClip || !unetMnnFile.exists() || !vaeDecoderMnnFile.exists()) { + Log.e(TAG, "MNN model files missing for CPU mode") + updateState(BackendState.Error("CPU model files missing")) + updateNotification("Error: CPU model files missing") + return@launch + } + + // Always pass clip.mnn path - binary auto-detects clip_v2.mnn and sets use_clip_v2=true + // If we pass clip_v2.mnn directly, use_clip_v2 stays false and causes crash! + val clipPath = clipMnnFile.absolutePath + + command = mutableListOf( + executableFile.absolutePath, + "--clip", clipPath, + "--unet", unetMnnFile.absolutePath, + "--vae_decoder", vaeDecoderMnnFile.absolutePath, + "--tokenizer", tokenizerFile.absolutePath, + "--port", SERVER_PORT.toString(), + "--text_embedding_size", "768", + "--cpu" + ) + + // Add VAE encoder if exists (for img2img) + val vaeEncoderMnnFile = File(modelsDir, "vae_encoder.mnn") + if (vaeEncoderMnnFile.exists()) { + command.addAll(listOf("--vae_encoder", vaeEncoderMnnFile.absolutePath)) + } + } else { + // NPU mode - use QNN models + // Note: The binary automatically upgrades from clip.mnn to clip_v2.mnn if found + // So we always pass clip.mnn path, and binary will find clip_v2.mnn + val clipBinFile = File(modelsDir, "clip.bin") + val clipMnnFile = File(modelsDir, "clip.mnn") // Binary expects this path + val clipV2MnnFile = File(modelsDir, "clip_v2.mnn") + + val unetFile = File(modelsDir, "unet.bin") + val vaeDecoderFile = File(modelsDir, "vae_decoder.bin") + val tokenizerFile = File(modelsDir, "tokenizer.json") + + // Check if we have clip (binary will auto-upgrade from clip.mnn path to clip_v2.mnn) + val hasClip = clipBinFile.exists() || clipV2MnnFile.exists() || clipMnnFile.exists() + + if (!hasClip || !unetFile.exists() || !vaeDecoderFile.exists()) { + Log.e(TAG, "NPU model files missing in: ${modelsDir.absolutePath}") + updateState(BackendState.Error("NPU model files missing")) + updateNotification("Error: NPU model files missing") + return@launch + } + + // Determine clip path: use clip.bin if exists, otherwise clip.mnn (binary auto-upgrades to clip_v2.mnn) + val clipPath = if (clipBinFile.exists()) clipBinFile.absolutePath else clipMnnFile.absolutePath + val useCpuClip = !clipBinFile.exists() && (clipV2MnnFile.exists() || clipMnnFile.exists()) + + command = mutableListOf( + executableFile.absolutePath, + "--clip", clipPath, + "--unet", unetFile.absolutePath, + "--vae_decoder", vaeDecoderFile.absolutePath, + "--tokenizer", tokenizerFile.absolutePath, + "--backend", File(runtimeDir, "libQnnHtp.so").absolutePath, + "--system_library", File(runtimeDir, "libQnnSystem.so").absolutePath, + "--port", SERVER_PORT.toString(), + "--text_embedding_size", "768" + ) + + // Add VAE encoder if exists (for img2img) + val vaeEncoderFile = File(modelsDir, "vae_encoder.bin") + if (vaeEncoderFile.exists()) { + command.addAll(listOf("--vae_encoder", vaeEncoderFile.absolutePath)) + } + + // Add patch file if resolution differs from 512 + if (width != 512 || height != 512) { + val patchFile = if (width == height) { + val squarePatch = File(modelsDir, "${width}.patch") + if (squarePatch.exists()) squarePatch else File(modelsDir, "${width}x${height}.patch") + } else { + File(modelsDir, "${width}x${height}.patch") + } + + if (patchFile.exists()) { + command.addAll(listOf("--patch", patchFile.absolutePath)) + Log.i(TAG, "Using patch file: ${patchFile.name}") + } else { + Log.w(TAG, "Patch file not found: ${patchFile.absolutePath}, using 512×512") + } + } + + // Add CPU clip flag if using MNN clip model + if (useCpuClip) { + command.add("--use_cpu_clip") + } + } + + // Set up environment + val env = mutableMapOf() + val systemLibPaths = mutableListOf( + runtimeDir.absolutePath, + "/system/lib64", + "/vendor/lib64", + "/vendor/lib64/egl" + ) + env["LD_LIBRARY_PATH"] = systemLibPaths.joinToString(":") + env["DSP_LIBRARY_PATH"] = runtimeDir.absolutePath + + Log.d(TAG, "COMMAND: ${command.joinToString(" ")}") + Log.d(TAG, "LD_LIBRARY_PATH=${env["LD_LIBRARY_PATH"]}") + Log.d(TAG, "DSP_LIBRARY_PATH=${env["DSP_LIBRARY_PATH"]}") + + val processBuilder = ProcessBuilder(command).apply { + directory(File(nativeDir)) + redirectErrorStream(true) + environment().putAll(env) + } + + backendProcess = processBuilder.start() + startMonitorThread() + + // Wait for server to be ready + delay(2000) + updateState(BackendState.Running) + updateNotification(if (runOnCpu) "Running on CPU (port $SERVER_PORT)" else "Running on NPU (port $SERVER_PORT)") + + } catch (e: Exception) { + Log.e(TAG, "Backend start failed", e) + updateState(BackendState.Error("Backend start failed: ${e.message}")) + updateNotification("Error: ${e.message}") + } + } + } + + private fun startMonitorThread() { + Thread { + try { + backendProcess?.let { proc -> + proc.inputStream.bufferedReader().use { reader -> + var line: String? + while (reader.readLine().also { line = it } != null) { + Log.i(TAG, "Backend: $line") + } + } + + val exitCode = proc.waitFor() + Log.i(TAG, "Backend process exited with code: $exitCode") + updateState(BackendState.Error("Process exited: $exitCode")) + updateNotification("Process exited: $exitCode") + } + } catch (e: Exception) { + Log.e(TAG, "Monitor error", e) + updateState(BackendState.Error("Monitor error: ${e.message}")) + } + }.apply { + isDaemon = true + start() + } + } + + private fun stopBackend() { + Log.i(TAG, "Stopping backend") + backendProcess?.let { proc -> + try { + proc.destroy() + if (!proc.waitFor(5, TimeUnit.SECONDS)) { + proc.destroyForcibly() + } + Log.i(TAG, "Process ended, code: ${proc.exitValue()}") + updateState(BackendState.Idle) + } catch (e: Exception) { + Log.e(TAG, "Stop error", e) + updateState(BackendState.Error("Stop error: ${e.message}")) + } finally { + backendProcess = null + } + } + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + NOTIFICATION_CHANNEL_ID, + "QNN Backend Service", + NotificationManager.IMPORTANCE_LOW + ).apply { + description = "Qualcomm NPU inference service" + setShowBadge(false) + } + + val notificationManager = getSystemService(NotificationManager::class.java) + notificationManager.createNotificationChannel(channel) + } + } + + private fun createNotification(status: String): Notification { + return NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) + .setContentTitle("QNN Backend") + .setContentText(status) + .setSmallIcon(android.R.drawable.ic_menu_manage) + .setOngoing(true) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .addAction( + android.R.drawable.ic_menu_close_clear_cancel, + "Stop", + PendingIntent.getService( + this, + 0, + createStopIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + .build() + } + + private fun updateNotification(status: String) { + val notification = createNotification(status) + val notificationManager = getSystemService(NotificationManager::class.java) + notificationManager.notify(NOTIFICATION_ID, notification) + } + + /** + * Find the actual model directory containing the model files. + * ZIP archives from HuggingFace may have nested structure like: + * output_512/qnn_models_8gen2/clip.bin + * + * This function recursively searches for a directory containing unet.bin + */ + private fun extractZipFile(zipFile: File, destinationDir: File) { + ZipFile(zipFile).use { zip -> + zip.entries().asSequence().forEach { entry -> + zip.getInputStream(entry).use { inputStream -> + val filePath = File(destinationDir, entry.name) + if (!entry.isDirectory) { + // Create parent directories if they don't exist + filePath.parentFile?.mkdirs() + BufferedOutputStream(FileOutputStream(filePath)).use { bos -> + val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE) + var read: Int + while (inputStream.read(bytesIn).also { read = it } != -1) { + bos.write(bytesIn, 0, read) + } + } + } else { + filePath.mkdirs() + } + } + } + } + } + + private fun findModelDirectory(baseDir: File): File? { + if (!baseDir.exists() || !baseDir.isDirectory) { + return null + } + + // Check for model.zip and extract if present + val zipFile = File(baseDir, "model.zip") + if (zipFile.exists()) { + try { + Log.i(TAG, "Found model.zip (${zipFile.length()} bytes), extracting...") + extractZipFile(zipFile, baseDir) + zipFile.delete() + Log.i(TAG, "Model extracted and zip deleted successfully") + } catch (e: Exception) { + Log.e(TAG, "Failed to extract model.zip", e) + // Delete corrupted zip file so user can re-download + try { + zipFile.delete() + Log.w(TAG, "Deleted corrupted model.zip") + } catch (deleteError: Exception) { + Log.e(TAG, "Failed to delete corrupted zip", deleteError) + } + return null + } + } + + // Check if model files exist directly in this directory + // For QNN (NPU): unet.bin, vae_decoder.bin + // For MNN (CPU): unet.mnn, vae_decoder.mnn + val hasQnnUnet = File(baseDir, "unet.bin").exists() + val hasQnnVaeDecoder = File(baseDir, "vae_decoder.bin").exists() + val hasMnnUnet = File(baseDir, "unet.mnn").exists() + val hasMnnVaeDecoder = File(baseDir, "vae_decoder.mnn").exists() + val hasTokenizer = File(baseDir, "tokenizer.json").exists() + + val hasQnnModel = hasQnnUnet && hasQnnVaeDecoder && hasTokenizer + val hasMnnModel = hasMnnUnet && hasMnnVaeDecoder && hasTokenizer + + if (hasQnnModel || hasMnnModel) { + return baseDir + } + + // Recursively search subdirectories (max depth 3) + return findModelDirectoryRecursive(baseDir, 0, 3) + } + + private fun findModelDirectoryRecursive(dir: File, depth: Int, maxDepth: Int): File? { + if (depth >= maxDepth) return null + + val subdirs = dir.listFiles { file -> file.isDirectory } ?: return null + + for (subdir in subdirs) { + // For QNN (NPU): unet.bin, vae_decoder.bin + // For MNN (CPU): unet.mnn, vae_decoder.mnn + val hasQnnUnet = File(subdir, "unet.bin").exists() + val hasQnnVaeDecoder = File(subdir, "vae_decoder.bin").exists() + val hasMnnUnet = File(subdir, "unet.mnn").exists() + val hasMnnVaeDecoder = File(subdir, "vae_decoder.mnn").exists() + val hasTokenizer = File(subdir, "tokenizer.json").exists() + + val hasQnnModel = hasQnnUnet && hasQnnVaeDecoder && hasTokenizer + val hasMnnModel = hasMnnUnet && hasMnnVaeDecoder && hasTokenizer + + if (hasQnnModel || hasMnnModel) { + return subdir + } + + // Search deeper + val found = findModelDirectoryRecursive(subdir, depth + 1, maxDepth) + if (found != null) { + return found + } + } + + return null + } +} diff --git a/feature/work/build.gradle.kts b/feature/work/build.gradle.kts index d7e637214..b24f8a8c6 100644 --- a/feature/work/build.gradle.kts +++ b/feature/work/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.feature.work" + namespace = "dev.minios.pdaiv1.feature.work" } dependencies { @@ -15,4 +15,5 @@ dependencies { implementation(libs.koin.android) implementation(libs.rx.kotlin) implementation(libs.androidx.work.runtime) + implementation(libs.google.gson) } diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt deleted file mode 100644 index c1444ec9b..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.shifthackz.aisdv1.work - -import androidx.work.ExistingWorkPolicy -import androidx.work.ListenableWorker -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.OutOfQuotaPolicy -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager -import com.shifthackz.aisdv1.work.di.WorkManagerProvider -import com.shifthackz.aisdv1.work.mappers.toByteArray -import com.shifthackz.aisdv1.work.task.ImageToImageTask -import com.shifthackz.aisdv1.work.task.TextToImageTask -import org.koin.java.KoinJavaComponent.inject -import java.io.File - -internal class BackgroundTaskManagerImpl : BackgroundTaskManager { - - override fun scheduleTextToImageTask(payload: TextToImagePayload) { - runWork(payload.toByteArray(), Constants.FILE_TEXT_TO_IMAGE) - } - - override fun scheduleImageToImageTask(payload: ImageToImagePayload) { - runWork(payload.toByteArray(), Constants.FILE_IMAGE_TO_IMAGE) - } - - override fun retryLastTextToImageTask(): Result { - try { - val bytes = readPayload(Constants.FILE_TEXT_TO_IMAGE) - ?: return Result.failure(Throwable("Payload is null.")) - runWork(bytes, Constants.FILE_TEXT_TO_IMAGE) - return Result.success(Unit) - } catch (e: Exception) { - return Result.failure(e) - } - } - - override fun retryLastImageToImageTask(): Result { - try { - val bytes = readPayload(Constants.FILE_IMAGE_TO_IMAGE) - ?: return Result.failure(Throwable("Payload is null.")) - runWork(bytes, Constants.FILE_IMAGE_TO_IMAGE) - return Result.success(Unit) - } catch (e: Exception) { - return Result.failure(e) - } - } - - override fun cancelAll(): Result = runCatching { - val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) - workManager().cancelAllWork() - } - - private inline fun runWork(bytes: ByteArray, fileName: String) { - val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) - val workRequest = OneTimeWorkRequestBuilder() - .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) - .addTag(Constants.TAG_GENERATION) - .build() - - writePayload(bytes, fileName) - workManager().cancelUniqueWork(Constants.TAG_GENERATION) - workManager().enqueueUniqueWork( - Constants.TAG_GENERATION, - ExistingWorkPolicy.REPLACE, - workRequest, - ) - } - - private fun readPayload(fileName: String): ByteArray? { - val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java) - val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath) - if (!cacheDirectory.exists()) { - return null - } - val outFile = File(cacheDirectory, fileName) - return outFile.readBytes() - } - - private fun writePayload(bytes: ByteArray, fileName: String) { - val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java) - val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath) - if (!cacheDirectory.exists()) cacheDirectory.mkdirs() - val outFile = File(cacheDirectory, fileName) - if (!outFile.exists()) outFile.createNewFile() - outFile.writeBytes(bytes) - } -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundWorkObserverImpl.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundWorkObserverImpl.kt deleted file mode 100644 index 6a78e5b38..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundWorkObserverImpl.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.shifthackz.aisdv1.work - -import androidx.work.WorkInfo -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.BackgroundWorkResult -import com.shifthackz.aisdv1.domain.entity.BackgroundWorkStatus -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.work.di.WorkManagerProvider -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.BehaviorSubject -import org.koin.java.KoinJavaComponent.inject - -internal class BackgroundWorkObserverImpl : BackgroundWorkObserver { - - private val stateSubject = BehaviorSubject.createDefault(false) - private val messageSubject = BehaviorSubject.createDefault("" to "") - private val resultSubject = BehaviorSubject.createDefault(BackgroundWorkResult.None) - - override fun observeStatus(): Flowable { - refreshStatus() - return Flowable.combineLatest( - stateSubject.toFlowable(BackpressureStrategy.LATEST), - messageSubject.toFlowable(BackpressureStrategy.LATEST), - ) { running, (title, subTitle) -> - BackgroundWorkStatus(running, title, subTitle) - } - } - - override fun observeResult(): Flowable { - return resultSubject.toFlowable(BackpressureStrategy.LATEST) - } - - override fun dismissResult() { - resultSubject.onNext(BackgroundWorkResult.None) - } - - override fun refreshStatus() { - val hasActive = hasActiveTasks() - if (hasActive) { - resultSubject.onNext(BackgroundWorkResult.None) - } - stateSubject.onNext(hasActive) - } - - override fun postStatusMessage(title: String, subTitle: String) { - stateSubject.onNext(true) - messageSubject.onNext(title to subTitle) - resultSubject.onNext(BackgroundWorkResult.None) - } - - override fun postSuccessSignal(result: List) { - stateSubject.onNext(false) - messageSubject.onNext("" to "") - resultSubject.onNext(BackgroundWorkResult.Success(result)) - } - - override fun postCancelSignal() { - stateSubject.onNext(false) - messageSubject.onNext("" to "") - resultSubject.onNext(BackgroundWorkResult.None) - } - - override fun postFailedSignal(t: Throwable) { - stateSubject.onNext(false) - messageSubject.onNext("" to "") - resultSubject.onNext(BackgroundWorkResult.Error(t)) - } - - override fun hasActiveTasks(): Boolean { - val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) - val workInfos = workManager().getWorkInfosByTag(Constants.TAG_GENERATION).get() - val isRunning = workInfos.any { workInfo -> - workInfo.state == WorkInfo.State.BLOCKED - || workInfo.state == WorkInfo.State.ENQUEUED - || workInfo.state == WorkInfo.State.RUNNING - } - return isRunning - } -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/Constants.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/Constants.kt deleted file mode 100644 index f6ca341e0..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/Constants.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.work - -internal object Constants { - - const val NOTIFICATION_TEXT_TO_IMAGE_FOREGROUND = 5598 - const val NOTIFICATION_TEXT_TO_IMAGE_GENERIC = 5599 - const val NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND = 151297 - const val NOTIFICATION_IMAGE_TO_IMAGE_GENERIC = 151298 - - const val TAG_GENERATION = "work_ai_generation" - - const val FILE_TEXT_TO_IMAGE = "txt2img.bin" - const val FILE_IMAGE_TO_IMAGE = "img2img.bin" -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/NotificationWorker.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/NotificationWorker.kt deleted file mode 100644 index 233c65d88..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/NotificationWorker.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.shifthackz.aisdv1.work.core - -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.work.ForegroundInfo -import androidx.work.WorkManager -import androidx.work.WorkerParameters -import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider -import com.shifthackz.aisdv1.core.common.extensions.isAppInForeground -import com.shifthackz.aisdv1.core.notification.PushNotificationManager - -internal abstract class NotificationWorker( - context: Context, - workerParameters: WorkerParameters, - private val pushNotificationManager: PushNotificationManager, - private val activityIntentProvider: ActivityIntentProvider, -) : Rx3Worker(context, workerParameters) { - - abstract val notificationId: Int - - abstract val genericNotificationId: Int - - fun showGenericNotification(text: String, body: String?) { - pushNotificationManager.createNotificationChannel() - val notification = pushNotificationManager.createNotification(text, body) { - setContentIntent( - PendingIntent.getActivity( - applicationContext, - 0, - activityIntentProvider().also { intent -> - intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP - }, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, - ), - ) - setVibrate(longArrayOf(10000L, 2000L, 1000L)) - setAutoCancel(true) - setTicker(text) - setSilent(applicationContext.isAppInForeground()) - } - pushNotificationManager.show(genericNotificationId, notification) - } - - fun setForegroundNotification( - title: String, - body: String, - subText: String? = null, - progress: Pair? = null, - silent: Boolean = false, - canCancel: Boolean = false, - ) { - pushNotificationManager.createNotificationChannel() - val notification = pushNotificationManager.createNotification(title, body) { - setSilent(silent) - setTicker(title) - setOngoing(true) - - progress?.let { (step, max) -> - setProgress(max, step, false) - } ?: run { - setProgress(0, 0, true) - } - subText?.let { setSubText(it) } - - if (canCancel) { - val intent = WorkManager - .getInstance(applicationContext) - .createCancelPendingIntent(id) - addAction(android.R.drawable.ic_delete, "Cancel", intent) - } - - setAutoCancel(false) - setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) - - setContentIntent( - PendingIntent.getActivity( - applicationContext, - 0, - activityIntentProvider().also { intent -> - intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP - }, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, - ), - ) - } - - val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - ForegroundInfo(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) - } else { - ForegroundInfo(notificationId, notification) - } - setForegroundAsync(info) - } -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/BackgroundWorkModule.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/BackgroundWorkModule.kt deleted file mode 100644 index 713198ea3..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/BackgroundWorkModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.work.di - -import androidx.work.WorkManager -import androidx.work.WorkerFactory -import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.work.BackgroundTaskManagerImpl -import com.shifthackz.aisdv1.work.BackgroundWorkObserverImpl -import org.koin.android.ext.koin.androidApplication -import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.binds -import org.koin.dsl.module - -val backgroundWorkModule = module { - factory { - WorkManagerProvider { WorkManager.getInstance(androidApplication()) } - } - - factoryOf(::SdaiWorkerFactory) binds arrayOf(SdaiWorkerFactory::class, WorkerFactory::class) - singleOf(::BackgroundWorkObserverImpl) bind BackgroundWorkObserver::class - factoryOf(::BackgroundTaskManagerImpl) bind BackgroundTaskManager::class -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt deleted file mode 100644 index 68d419d1f..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.shifthackz.aisdv1.work.di - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerFactory -import androidx.work.WorkerParameters -import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase -import com.shifthackz.aisdv1.work.task.ImageToImageTask -import com.shifthackz.aisdv1.work.task.TextToImageTask - -class SdaiWorkerFactory( - private val backgroundWorkObserver: BackgroundWorkObserver, - private val pushNotificationManager: PushNotificationManager, - private val preferenceManager: PreferenceManager, - private val textToImageUseCase: TextToImageUseCase, - private val imageToImageUseCase: ImageToImageUseCase, - private val observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, - private val observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, - private val interruptGenerationUseCase: InterruptGenerationUseCase, - private val fileProviderDescriptor: FileProviderDescriptor, - private val activityIntentProvider: ActivityIntentProvider, -) : WorkerFactory() { - - override fun createWorker( - appContext: Context, - workerClassName: String, - workerParameters: WorkerParameters - ): ListenableWorker? { - return when (workerClassName) { - TextToImageTask::class.java.name -> TextToImageTask( - context = appContext, - workerParameters = workerParameters, - pushNotificationManager = pushNotificationManager, - activityIntentProvider = activityIntentProvider, - backgroundWorkObserver = backgroundWorkObserver, - preferenceManager = preferenceManager, - textToImageUseCase = textToImageUseCase, - observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, - interruptGenerationUseCase = interruptGenerationUseCase, - fileProviderDescriptor = fileProviderDescriptor, - ) - - ImageToImageTask::class.java.name -> ImageToImageTask( - context = appContext, - workerParameters = workerParameters, - pushNotificationManager = pushNotificationManager, - activityIntentProvider = activityIntentProvider, - backgroundWorkObserver = backgroundWorkObserver, - preferenceManager = preferenceManager, - imageToImageUseCase = imageToImageUseCase, - observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, - interruptGenerationUseCase = interruptGenerationUseCase, - fileProviderDescriptor = fileProviderDescriptor, - ) - - else -> null - } - } -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/WorkManagerProvider.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/WorkManagerProvider.kt deleted file mode 100644 index b4d24fda8..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/WorkManagerProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.work.di - -import androidx.work.WorkManager - -fun interface WorkManagerProvider { - operator fun invoke(): WorkManager -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/mappers/ImageToImagePayloadMappers.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/mappers/ImageToImagePayloadMappers.kt deleted file mode 100644 index 15c5aa7a3..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/mappers/ImageToImagePayloadMappers.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.work.mappers - -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.ObjectInputStream -import java.io.ObjectOutputStream - -internal fun ImageToImagePayload.toByteArray(): ByteArray { - val byteArrayOutputStream = ByteArrayOutputStream() - val objectOutputStream = ObjectOutputStream(byteArrayOutputStream) - objectOutputStream.writeObject(this) - objectOutputStream.close() - return byteArrayOutputStream.toByteArray() -} - -internal fun ByteArray.toImageToImagePayload(): ImageToImagePayload? { - val byteArrayInputStream = ByteArrayInputStream(this) - val objectInputStream = ObjectInputStream(byteArrayInputStream) - return objectInputStream.readObject() as? ImageToImagePayload -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/mappers/TextToImagePayloadMappers.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/mappers/TextToImagePayloadMappers.kt deleted file mode 100644 index fa34514ec..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/mappers/TextToImagePayloadMappers.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.work.mappers - -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.ObjectInputStream -import java.io.ObjectOutputStream - -internal fun TextToImagePayload.toByteArray(): ByteArray { - val byteArrayOutputStream = ByteArrayOutputStream() - val objectOutputStream = ObjectOutputStream(byteArrayOutputStream) - objectOutputStream.writeObject(this) - objectOutputStream.close() - return byteArrayOutputStream.toByteArray() -} - -internal fun ByteArray.toTextToImagePayload(): TextToImagePayload? { - val byteArrayInputStream = ByteArrayInputStream(this) - val objectInputStream = ObjectInputStream(byteArrayInputStream) - return objectInputStream.readObject() as? TextToImagePayload -} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt deleted file mode 100644 index c6b69bf82..000000000 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.shifthackz.aisdv1.work.task - -import android.content.Context -import androidx.work.WorkerParameters -import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.work.Constants -import com.shifthackz.aisdv1.work.Constants.NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND -import com.shifthackz.aisdv1.work.Constants.NOTIFICATION_IMAGE_TO_IMAGE_GENERIC -import com.shifthackz.aisdv1.work.core.CoreGenerationWorker -import com.shifthackz.aisdv1.work.mappers.toImageToImagePayload -import io.reactivex.rxjava3.core.Single -import java.io.File - -internal class ImageToImageTask( - context: Context, - workerParameters: WorkerParameters, - pushNotificationManager: PushNotificationManager, - activityIntentProvider: ActivityIntentProvider, - preferenceManager: PreferenceManager, - observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, - interruptGenerationUseCase: InterruptGenerationUseCase, - private val backgroundWorkObserver: BackgroundWorkObserver, - private val imageToImageUseCase: ImageToImageUseCase, - private val fileProviderDescriptor: FileProviderDescriptor, -) : CoreGenerationWorker( - context = context, - workerParameters = workerParameters, - pushNotificationManager = pushNotificationManager, - activityIntentProvider = activityIntentProvider, - preferenceManager = preferenceManager, - backgroundWorkObserver = backgroundWorkObserver, - observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, - interruptGenerationUseCase = interruptGenerationUseCase, -) { - - override val notificationId = NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND - - override val genericNotificationId = NOTIFICATION_IMAGE_TO_IMAGE_GENERIC - - override fun createWork(): Single { - handleStart() - backgroundWorkObserver.refreshStatus() - backgroundWorkObserver.dismissResult() - listenHordeStatus() - listenLocalDiffusionStatus() - return try { - val file = File(fileProviderDescriptor.workCacheDirPath, Constants.FILE_IMAGE_TO_IMAGE) - if (!file.exists()) { - handleError(Throwable("File is null.")) - compositeDisposable.clear() - return Single.just(Result.failure()) - } - - val bytes = file.readBytes() - val payload = bytes.toImageToImagePayload() - - if (payload == null) { - handleError(Throwable("Payload is null.")) - compositeDisposable.clear() - return Single.just(Result.failure()) - } - - listenHordeStatus() - - imageToImageUseCase(payload) - .doOnSubscribe { handleProcess() } - .map { result -> - handleSuccess(result) - Result.success() - } - .onErrorReturn { t -> - handleError(t) - Result.failure() - } - .doFinally { compositeDisposable.clear() } - } catch (e: Exception) { - handleError(e) - compositeDisposable.clear() - Single.just(Result.failure()) - } - } -} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/BackgroundTaskManagerImpl.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/BackgroundTaskManagerImpl.kt new file mode 100644 index 000000000..91a32636f --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/BackgroundTaskManagerImpl.kt @@ -0,0 +1,107 @@ +package dev.minios.pdaiv1.work + +import androidx.work.ExistingWorkPolicy +import androidx.work.ListenableWorker +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.OutOfQuotaPolicy +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.domain.entity.FalAiPayload +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.work.di.WorkManagerProvider +import dev.minios.pdaiv1.work.mappers.toByteArray +import dev.minios.pdaiv1.work.mappers.toByteArray as toFalAiByteArray +import dev.minios.pdaiv1.work.task.FalAiTask +import dev.minios.pdaiv1.work.task.ImageToImageTask +import dev.minios.pdaiv1.work.task.TextToImageTask +import org.koin.java.KoinJavaComponent.inject +import java.io.File + +internal class BackgroundTaskManagerImpl : BackgroundTaskManager { + + override fun scheduleTextToImageTask(payload: TextToImagePayload) { + runWork(payload.toByteArray(), Constants.FILE_TEXT_TO_IMAGE) + } + + override fun scheduleImageToImageTask(payload: ImageToImagePayload) { + runWork(payload.toByteArray(), Constants.FILE_IMAGE_TO_IMAGE) + } + + override fun scheduleFalAiTask(payload: FalAiPayload) { + runWork(payload.toByteArray(), Constants.FILE_FAL_AI) + } + + override fun retryLastTextToImageTask(): Result { + try { + val bytes = readPayload(Constants.FILE_TEXT_TO_IMAGE) + ?: return Result.failure(Throwable("Payload is null.")) + runWork(bytes, Constants.FILE_TEXT_TO_IMAGE) + return Result.success(Unit) + } catch (e: Exception) { + return Result.failure(e) + } + } + + override fun retryLastImageToImageTask(): Result { + try { + val bytes = readPayload(Constants.FILE_IMAGE_TO_IMAGE) + ?: return Result.failure(Throwable("Payload is null.")) + runWork(bytes, Constants.FILE_IMAGE_TO_IMAGE) + return Result.success(Unit) + } catch (e: Exception) { + return Result.failure(e) + } + } + + override fun retryLastFalAiTask(): Result { + try { + val bytes = readPayload(Constants.FILE_FAL_AI) + ?: return Result.failure(Throwable("Payload is null.")) + runWork(bytes, Constants.FILE_FAL_AI) + return Result.success(Unit) + } catch (e: Exception) { + return Result.failure(e) + } + } + + override fun cancelAll(): Result = runCatching { + val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) + workManager().cancelAllWork() + } + + private inline fun runWork(bytes: ByteArray, fileName: String) { + val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) + val workRequest = OneTimeWorkRequestBuilder() + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .addTag(Constants.TAG_GENERATION) + .build() + + writePayload(bytes, fileName) + workManager().cancelUniqueWork(Constants.TAG_GENERATION) + workManager().enqueueUniqueWork( + Constants.TAG_GENERATION, + ExistingWorkPolicy.REPLACE, + workRequest, + ) + } + + private fun readPayload(fileName: String): ByteArray? { + val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java) + val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath) + if (!cacheDirectory.exists()) { + return null + } + val outFile = File(cacheDirectory, fileName) + return outFile.readBytes() + } + + private fun writePayload(bytes: ByteArray, fileName: String) { + val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java) + val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath) + if (!cacheDirectory.exists()) cacheDirectory.mkdirs() + val outFile = File(cacheDirectory, fileName) + if (!outFile.exists()) outFile.createNewFile() + outFile.writeBytes(bytes) + } +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/BackgroundWorkObserverImpl.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/BackgroundWorkObserverImpl.kt new file mode 100644 index 000000000..d09a91ab4 --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/BackgroundWorkObserverImpl.kt @@ -0,0 +1,98 @@ +package dev.minios.pdaiv1.work + +import androidx.work.WorkInfo +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.BackgroundWorkResult +import dev.minios.pdaiv1.domain.entity.BackgroundWorkStatus +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.work.di.WorkManagerProvider +import io.reactivex.rxjava3.core.BackpressureStrategy +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import org.koin.java.KoinJavaComponent.inject + +internal class BackgroundWorkObserverImpl : BackgroundWorkObserver { + + private val stateSubject = BehaviorSubject.createDefault(false) + private val messageSubject = BehaviorSubject.createDefault("" to "") + private val resultSubject = BehaviorSubject.createDefault(BackgroundWorkResult.None) + private val newImageSubject = io.reactivex.rxjava3.subjects.PublishSubject.create() + private val galleryChangedSubject = io.reactivex.rxjava3.subjects.PublishSubject.create() + + override fun observeStatus(): Flowable { + refreshStatus() + return Flowable.combineLatest( + stateSubject.toFlowable(BackpressureStrategy.LATEST), + messageSubject.toFlowable(BackpressureStrategy.LATEST), + ) { running, (title, subTitle) -> + BackgroundWorkStatus(running, title, subTitle) + } + } + + override fun observeResult(): Flowable { + return resultSubject.toFlowable(BackpressureStrategy.LATEST) + } + + override fun observeNewImage(): Flowable { + return newImageSubject.toFlowable(BackpressureStrategy.BUFFER) + } + + override fun observeGalleryChanged(): Flowable { + return galleryChangedSubject.toFlowable(BackpressureStrategy.BUFFER) + } + + override fun dismissResult() { + resultSubject.onNext(BackgroundWorkResult.None) + } + + override fun refreshStatus() { + val hasActive = hasActiveTasks() + if (hasActive) { + resultSubject.onNext(BackgroundWorkResult.None) + } + stateSubject.onNext(hasActive) + } + + override fun postStatusMessage(title: String, subTitle: String) { + stateSubject.onNext(true) + messageSubject.onNext(title to subTitle) + resultSubject.onNext(BackgroundWorkResult.None) + } + + override fun postSuccessSignal(result: List) { + stateSubject.onNext(false) + messageSubject.onNext("" to "") + resultSubject.onNext(BackgroundWorkResult.Success(result)) + } + + override fun postNewImageSignal() { + newImageSubject.onNext(Unit) + } + + override fun postGalleryChangedSignal() { + galleryChangedSubject.onNext(Unit) + } + + override fun postCancelSignal() { + stateSubject.onNext(false) + messageSubject.onNext("" to "") + resultSubject.onNext(BackgroundWorkResult.None) + } + + override fun postFailedSignal(t: Throwable) { + stateSubject.onNext(false) + messageSubject.onNext("" to "") + resultSubject.onNext(BackgroundWorkResult.Error(t)) + } + + override fun hasActiveTasks(): Boolean { + val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) + val workInfos = workManager().getWorkInfosByTag(Constants.TAG_GENERATION).get() + val isRunning = workInfos.any { workInfo -> + workInfo.state == WorkInfo.State.BLOCKED + || workInfo.state == WorkInfo.State.ENQUEUED + || workInfo.state == WorkInfo.State.RUNNING + } + return isRunning + } +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/Constants.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/Constants.kt new file mode 100644 index 000000000..f7e40aea4 --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/Constants.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.work + +internal object Constants { + + const val NOTIFICATION_TEXT_TO_IMAGE_FOREGROUND = 5598 + const val NOTIFICATION_TEXT_TO_IMAGE_GENERIC = 5599 + const val NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND = 151297 + const val NOTIFICATION_IMAGE_TO_IMAGE_GENERIC = 151298 + const val NOTIFICATION_FAL_AI_FOREGROUND = 161300 + const val NOTIFICATION_FAL_AI_GENERIC = 161301 + + const val TAG_GENERATION = "work_ai_generation" + + const val FILE_TEXT_TO_IMAGE = "txt2img.bin" + const val FILE_IMAGE_TO_IMAGE = "img2img.bin" + const val FILE_FAL_AI = "falai.bin" +} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/core/CoreGenerationWorker.kt similarity index 82% rename from feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt rename to feature/work/src/main/java/dev/minios/pdaiv1/work/core/CoreGenerationWorker.kt index b77330eea..608f9d1f6 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/core/CoreGenerationWorker.kt @@ -1,24 +1,24 @@ @file:Suppress("MemberVisibilityCanBePrivate") -package com.shifthackz.aisdv1.work.core +package dev.minios.pdaiv1.work.core import android.content.Context import androidx.work.WorkerParameters -import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider -import com.shifthackz.aisdv1.core.common.contract.RxDisposableContract -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.localization.formatter.DurationFormatter -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.core.common.appbuild.ActivityIntentProvider +import dev.minios.pdaiv1.core.common.contract.RxDisposableContract +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.localization.formatter.DurationFormatter +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.subscribeBy -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR internal abstract class CoreGenerationWorker( context: Context, @@ -87,7 +87,7 @@ internal abstract class CoreGenerationWorker( status.current.toString(), status.total.toString() ) - backgroundWorkObserver.postStatusMessage(title, subTitle) + // Only update system notification with progress bar, don't spam UI widget setForegroundNotification( title = title, body = subTitle, @@ -119,6 +119,7 @@ internal abstract class CoreGenerationWorker( protected fun handleSuccess(result: List) { backgroundWorkObserver.refreshStatus() backgroundWorkObserver.postSuccessSignal(result) + backgroundWorkObserver.postGalleryChangedSignal() val title = applicationContext.getString(LocalizationR.string.notification_finish_title) val subTitle = applicationContext.getString(LocalizationR.string.notification_finish_sub_title) showGenericNotification(title, subTitle) diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/core/NotificationWorker.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/core/NotificationWorker.kt new file mode 100644 index 000000000..e648517fe --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/core/NotificationWorker.kt @@ -0,0 +1,129 @@ +package dev.minios.pdaiv1.work.core + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.work.ForegroundInfo +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import dev.minios.pdaiv1.core.common.appbuild.ActivityIntentProvider +import dev.minios.pdaiv1.core.common.extensions.isAppInForeground +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import io.reactivex.rxjava3.core.Single + +internal abstract class NotificationWorker( + context: Context, + workerParameters: WorkerParameters, + private val pushNotificationManager: PushNotificationManager, + private val activityIntentProvider: ActivityIntentProvider, +) : Rx3Worker(context, workerParameters) { + + abstract val notificationId: Int + + abstract val genericNotificationId: Int + + override val foregroundInfo: Single + get() = Single.fromCallable { + pushNotificationManager.createNotificationChannel() + val notification = pushNotificationManager.createNotification( + "Generation in progress", + "Your image is being generated..." + ) { + setOngoing(true) + setProgress(0, 0, true) + setAutoCancel(false) + setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) + setContentIntent( + PendingIntent.getActivity( + applicationContext, + 0, + activityIntentProvider().also { intent -> + intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + ), + ) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForegroundInfo(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) + } else { + ForegroundInfo(notificationId, notification) + } + } + + fun showGenericNotification(text: String, body: String?) { + pushNotificationManager.createNotificationChannel() + val notification = pushNotificationManager.createNotification(text, body) { + setContentIntent( + PendingIntent.getActivity( + applicationContext, + 0, + activityIntentProvider().also { intent -> + intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + ), + ) + setVibrate(longArrayOf(10000L, 2000L, 1000L)) + setAutoCancel(true) + setTicker(text) + setSilent(applicationContext.isAppInForeground()) + } + pushNotificationManager.show(genericNotificationId, notification) + } + + fun setForegroundNotification( + title: String, + body: String, + subText: String? = null, + progress: Pair? = null, + silent: Boolean = false, + canCancel: Boolean = false, + ) { + pushNotificationManager.createNotificationChannel() + // Use progress notification channel to avoid heads-up notifications + val notification = pushNotificationManager.createProgressNotification(title, body) { + setSilent(silent) + setTicker(title) + setOngoing(true) + + progress?.let { (step, max) -> + setProgress(max, step, false) + } ?: run { + setProgress(0, 0, true) + } + subText?.let { setSubText(it) } + + if (canCancel) { + val intent = WorkManager + .getInstance(applicationContext) + .createCancelPendingIntent(id) + addAction(android.R.drawable.ic_delete, "Cancel", intent) + } + + setAutoCancel(false) + setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) + + setContentIntent( + PendingIntent.getActivity( + applicationContext, + 0, + activityIntentProvider().also { intent -> + intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + ), + ) + } + + val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForegroundInfo(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) + } else { + ForegroundInfo(notificationId, notification) + } + setForegroundAsync(info) + } +} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/Rx3Worker.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/core/Rx3Worker.kt similarity index 97% rename from feature/work/src/main/java/com/shifthackz/aisdv1/work/core/Rx3Worker.kt rename to feature/work/src/main/java/dev/minios/pdaiv1/work/core/Rx3Worker.kt index ea71bdc35..71b974d6d 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/Rx3Worker.kt +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/core/Rx3Worker.kt @@ -1,6 +1,6 @@ @file:Suppress("MemberVisibilityCanBePrivate") -package com.shifthackz.aisdv1.work.core +package dev.minios.pdaiv1.work.core import android.content.Context import androidx.annotation.MainThread @@ -61,7 +61,7 @@ internal abstract class Rx3Worker( return convert(SingleFutureAdapter(), foregroundInfo) } - val foregroundInfo: Single + open val foregroundInfo: Single get() { val message = ( "Expedited WorkRequests require a RxWorker to provide an implementation for" diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/di/BackgroundWorkModule.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/di/BackgroundWorkModule.kt new file mode 100644 index 000000000..70862c59e --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/di/BackgroundWorkModule.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.work.di + +import androidx.work.WorkManager +import androidx.work.WorkerFactory +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.work.BackgroundTaskManagerImpl +import dev.minios.pdaiv1.work.BackgroundWorkObserverImpl +import org.koin.android.ext.koin.androidApplication +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.binds +import org.koin.dsl.module + +val backgroundWorkModule = module { + factory { + WorkManagerProvider { WorkManager.getInstance(androidApplication()) } + } + + factoryOf(::PdaiWorkerFactory) binds arrayOf(PdaiWorkerFactory::class, WorkerFactory::class) + singleOf(::BackgroundWorkObserverImpl) bind BackgroundWorkObserver::class + factoryOf(::BackgroundTaskManagerImpl) bind BackgroundTaskManager::class +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/di/PdaiWorkerFactory.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/di/PdaiWorkerFactory.kt new file mode 100644 index 000000000..64400f04e --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/di/PdaiWorkerFactory.kt @@ -0,0 +1,87 @@ +package dev.minios.pdaiv1.work.di + +import android.content.Context +import androidx.work.ListenableWorker +import androidx.work.WorkerFactory +import androidx.work.WorkerParameters +import dev.minios.pdaiv1.core.common.appbuild.ActivityIntentProvider +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.generation.FalAiGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ImageToImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.TextToImageUseCase +import dev.minios.pdaiv1.work.task.FalAiTask +import dev.minios.pdaiv1.work.task.ImageToImageTask +import dev.minios.pdaiv1.work.task.TextToImageTask + +class PdaiWorkerFactory( + private val backgroundWorkObserver: BackgroundWorkObserver, + private val pushNotificationManager: PushNotificationManager, + private val preferenceManager: PreferenceManager, + private val textToImageUseCase: TextToImageUseCase, + private val imageToImageUseCase: ImageToImageUseCase, + private val falAiGenerationUseCase: FalAiGenerationUseCase, + private val observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, + private val observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + private val interruptGenerationUseCase: InterruptGenerationUseCase, + private val fileProviderDescriptor: FileProviderDescriptor, + private val activityIntentProvider: ActivityIntentProvider, +) : WorkerFactory() { + + override fun createWorker( + appContext: Context, + workerClassName: String, + workerParameters: WorkerParameters + ): ListenableWorker? { + return when (workerClassName) { + TextToImageTask::class.java.name -> TextToImageTask( + context = appContext, + workerParameters = workerParameters, + pushNotificationManager = pushNotificationManager, + activityIntentProvider = activityIntentProvider, + backgroundWorkObserver = backgroundWorkObserver, + preferenceManager = preferenceManager, + textToImageUseCase = textToImageUseCase, + observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, + fileProviderDescriptor = fileProviderDescriptor, + ) + + ImageToImageTask::class.java.name -> ImageToImageTask( + context = appContext, + workerParameters = workerParameters, + pushNotificationManager = pushNotificationManager, + activityIntentProvider = activityIntentProvider, + backgroundWorkObserver = backgroundWorkObserver, + preferenceManager = preferenceManager, + imageToImageUseCase = imageToImageUseCase, + observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, + fileProviderDescriptor = fileProviderDescriptor, + ) + + FalAiTask::class.java.name -> FalAiTask( + context = appContext, + workerParameters = workerParameters, + pushNotificationManager = pushNotificationManager, + activityIntentProvider = activityIntentProvider, + backgroundWorkObserver = backgroundWorkObserver, + preferenceManager = preferenceManager, + falAiGenerationUseCase = falAiGenerationUseCase, + observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, + fileProviderDescriptor = fileProviderDescriptor, + ) + + else -> null + } + } +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/di/WorkManagerProvider.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/di/WorkManagerProvider.kt new file mode 100644 index 000000000..14f6b7139 --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/di/WorkManagerProvider.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.work.di + +import androidx.work.WorkManager + +fun interface WorkManagerProvider { + operator fun invoke(): WorkManager +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/FalAiPayloadMappers.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/FalAiPayloadMappers.kt new file mode 100644 index 000000000..fb3ca1c52 --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/FalAiPayloadMappers.kt @@ -0,0 +1,36 @@ +package dev.minios.pdaiv1.work.mappers + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import dev.minios.pdaiv1.domain.entity.FalAiPayload + +private val gson = Gson() + +internal fun FalAiPayload.toByteArray(): ByteArray { + val json = gson.toJson(mapOf( + "endpointId" to endpointId, + "parameters" to parameters, + )) + return json.toByteArray(Charsets.UTF_8) +} + +internal fun FalAiPayload.toFalAiPayloadByteArray(): ByteArray = toByteArray() + +internal fun ByteArray.toFalAiPayload(): FalAiPayload? { + return try { + val json = toString(Charsets.UTF_8) + val type = object : TypeToken>() {}.type + val map: Map = gson.fromJson(json, type) + + val endpointId = map["endpointId"] as? String ?: return null + @Suppress("UNCHECKED_CAST") + val parameters = map["parameters"] as? Map ?: emptyMap() + + FalAiPayload( + endpointId = endpointId, + parameters = parameters, + ) + } catch (e: Exception) { + null + } +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/ImageToImagePayloadMappers.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/ImageToImagePayloadMappers.kt new file mode 100644 index 000000000..9cd0995cb --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/ImageToImagePayloadMappers.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.work.mappers + +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream + +internal fun ImageToImagePayload.toByteArray(): ByteArray { + val byteArrayOutputStream = ByteArrayOutputStream() + val objectOutputStream = ObjectOutputStream(byteArrayOutputStream) + objectOutputStream.writeObject(this) + objectOutputStream.close() + return byteArrayOutputStream.toByteArray() +} + +internal fun ByteArray.toImageToImagePayload(): ImageToImagePayload? { + val byteArrayInputStream = ByteArrayInputStream(this) + val objectInputStream = ObjectInputStream(byteArrayInputStream) + return objectInputStream.readObject() as? ImageToImagePayload +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/TextToImagePayloadMappers.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/TextToImagePayloadMappers.kt new file mode 100644 index 000000000..b3fb68e75 --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/mappers/TextToImagePayloadMappers.kt @@ -0,0 +1,21 @@ +package dev.minios.pdaiv1.work.mappers + +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream + +internal fun TextToImagePayload.toByteArray(): ByteArray { + val byteArrayOutputStream = ByteArrayOutputStream() + val objectOutputStream = ObjectOutputStream(byteArrayOutputStream) + objectOutputStream.writeObject(this) + objectOutputStream.close() + return byteArrayOutputStream.toByteArray() +} + +internal fun ByteArray.toTextToImagePayload(): TextToImagePayload? { + val byteArrayInputStream = ByteArrayInputStream(this) + val objectInputStream = ObjectInputStream(byteArrayInputStream) + return objectInputStream.readObject() as? TextToImagePayload +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/task/FalAiTask.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/task/FalAiTask.kt new file mode 100644 index 000000000..ebdf799e7 --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/task/FalAiTask.kt @@ -0,0 +1,114 @@ +package dev.minios.pdaiv1.work.task + +import android.content.Context +import androidx.work.WorkerParameters +import dev.minios.pdaiv1.core.common.appbuild.ActivityIntentProvider +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.generation.FalAiGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.work.Constants +import dev.minios.pdaiv1.work.Constants.NOTIFICATION_FAL_AI_FOREGROUND +import dev.minios.pdaiv1.work.Constants.NOTIFICATION_FAL_AI_GENERIC +import dev.minios.pdaiv1.work.core.CoreGenerationWorker +import dev.minios.pdaiv1.work.mappers.toFalAiPayload +import io.reactivex.rxjava3.core.Single +import java.io.File + +internal class FalAiTask( + context: Context, + workerParameters: WorkerParameters, + pushNotificationManager: PushNotificationManager, + activityIntentProvider: ActivityIntentProvider, + observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase: InterruptGenerationUseCase, + private val preferenceManager: PreferenceManager, + private val backgroundWorkObserver: BackgroundWorkObserver, + private val falAiGenerationUseCase: FalAiGenerationUseCase, + private val fileProviderDescriptor: FileProviderDescriptor, +) : CoreGenerationWorker( + context = context, + workerParameters = workerParameters, + pushNotificationManager = pushNotificationManager, + activityIntentProvider = activityIntentProvider, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, +) { + + override val notificationId: Int = NOTIFICATION_FAL_AI_FOREGROUND + + override val genericNotificationId: Int = NOTIFICATION_FAL_AI_GENERIC + + override fun createWork(): Single { + if (preferenceManager.backgroundProcessCount > 0) { + handleProcess() + handleError(Throwable("Background process count > 0")) + compositeDisposable.clear() + preferenceManager.backgroundProcessCount = 0 + debugLog("Background process count > 0! Skipping task.") + return Single.just(Result.failure()) + } + + preferenceManager.backgroundProcessCount++ + handleStart() + backgroundWorkObserver.refreshStatus() + backgroundWorkObserver.dismissResult() + debugLog("Starting FalAiTask!") + + return try { + val file = File(fileProviderDescriptor.workCacheDirPath, Constants.FILE_FAL_AI) + if (!file.exists()) { + preferenceManager.backgroundProcessCount-- + val t = Throwable("File is null.") + handleError(t) + compositeDisposable.clear() + errorLog(t, "Payload file does not exist.") + return Single.just(Result.failure()) + } + + val bytes = file.readBytes() + val payload = bytes.toFalAiPayload() + + if (payload == null) { + preferenceManager.backgroundProcessCount-- + val t = Throwable("Payload is null.") + handleError(t) + compositeDisposable.clear() + errorLog(t, "Payload was failed to read/parse.") + return Single.just(Result.failure()) + } + + falAiGenerationUseCase(payload) + .doOnSubscribe { handleProcess() } + .map { result -> + preferenceManager.backgroundProcessCount-- + handleSuccess(result) + debugLog("Fal AI generation finished successfully!") + Result.success() + } + .onErrorReturn { t -> + preferenceManager.backgroundProcessCount-- + handleError(t) + errorLog(t, "Caught exception from FalAiGenerationUseCase!") + Result.failure() + } + .doFinally { compositeDisposable.clear() } + } catch (e: Exception) { + preferenceManager.backgroundProcessCount-- + handleError(e) + compositeDisposable.clear() + errorLog(e, "Caught exception from FalAiTask worker!") + Single.just(Result.failure()) + } + } +} diff --git a/feature/work/src/main/java/dev/minios/pdaiv1/work/task/ImageToImageTask.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/task/ImageToImageTask.kt new file mode 100644 index 000000000..5424cba65 --- /dev/null +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/task/ImageToImageTask.kt @@ -0,0 +1,92 @@ +package dev.minios.pdaiv1.work.task + +import android.content.Context +import androidx.work.WorkerParameters +import dev.minios.pdaiv1.core.common.appbuild.ActivityIntentProvider +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.generation.ImageToImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.work.Constants +import dev.minios.pdaiv1.work.Constants.NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND +import dev.minios.pdaiv1.work.Constants.NOTIFICATION_IMAGE_TO_IMAGE_GENERIC +import dev.minios.pdaiv1.work.core.CoreGenerationWorker +import dev.minios.pdaiv1.work.mappers.toImageToImagePayload +import io.reactivex.rxjava3.core.Single +import java.io.File + +internal class ImageToImageTask( + context: Context, + workerParameters: WorkerParameters, + pushNotificationManager: PushNotificationManager, + activityIntentProvider: ActivityIntentProvider, + preferenceManager: PreferenceManager, + observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase: InterruptGenerationUseCase, + private val backgroundWorkObserver: BackgroundWorkObserver, + private val imageToImageUseCase: ImageToImageUseCase, + private val fileProviderDescriptor: FileProviderDescriptor, +) : CoreGenerationWorker( + context = context, + workerParameters = workerParameters, + pushNotificationManager = pushNotificationManager, + activityIntentProvider = activityIntentProvider, + preferenceManager = preferenceManager, + backgroundWorkObserver = backgroundWorkObserver, + observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, +) { + + override val notificationId = NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND + + override val genericNotificationId = NOTIFICATION_IMAGE_TO_IMAGE_GENERIC + + override fun createWork(): Single { + handleStart() + backgroundWorkObserver.refreshStatus() + backgroundWorkObserver.dismissResult() + listenHordeStatus() + listenLocalDiffusionStatus() + return try { + val file = File(fileProviderDescriptor.workCacheDirPath, Constants.FILE_IMAGE_TO_IMAGE) + if (!file.exists()) { + handleError(Throwable("File is null.")) + compositeDisposable.clear() + return Single.just(Result.failure()) + } + + val bytes = file.readBytes() + val payload = bytes.toImageToImagePayload() + + if (payload == null) { + handleError(Throwable("Payload is null.")) + compositeDisposable.clear() + return Single.just(Result.failure()) + } + + listenHordeStatus() + + imageToImageUseCase(payload) + .doOnSubscribe { handleProcess() } + .map { result -> + handleSuccess(result) + Result.success() + } + .onErrorReturn { t -> + handleError(t) + Result.failure() + } + .doFinally { compositeDisposable.clear() } + } catch (e: Exception) { + handleError(e) + compositeDisposable.clear() + Single.just(Result.failure()) + } + } +} diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt b/feature/work/src/main/java/dev/minios/pdaiv1/work/task/TextToImageTask.kt similarity index 78% rename from feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt rename to feature/work/src/main/java/dev/minios/pdaiv1/work/task/TextToImageTask.kt index f9e686315..e37f71ebb 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt +++ b/feature/work/src/main/java/dev/minios/pdaiv1/work/task/TextToImageTask.kt @@ -1,23 +1,23 @@ -package com.shifthackz.aisdv1.work.task +package dev.minios.pdaiv1.work.task import android.content.Context import androidx.work.WorkerParameters -import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase -import com.shifthackz.aisdv1.work.Constants -import com.shifthackz.aisdv1.work.Constants.NOTIFICATION_TEXT_TO_IMAGE_FOREGROUND -import com.shifthackz.aisdv1.work.Constants.NOTIFICATION_TEXT_TO_IMAGE_GENERIC -import com.shifthackz.aisdv1.work.core.CoreGenerationWorker -import com.shifthackz.aisdv1.work.mappers.toTextToImagePayload +import dev.minios.pdaiv1.core.common.appbuild.ActivityIntentProvider +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.TextToImageUseCase +import dev.minios.pdaiv1.work.Constants +import dev.minios.pdaiv1.work.Constants.NOTIFICATION_TEXT_TO_IMAGE_FOREGROUND +import dev.minios.pdaiv1.work.Constants.NOTIFICATION_TEXT_TO_IMAGE_GENERIC +import dev.minios.pdaiv1.work.core.CoreGenerationWorker +import dev.minios.pdaiv1.work.mappers.toTextToImagePayload import io.reactivex.rxjava3.core.Single import java.io.File diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 184c54499..76bddc409 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,128 +1,133 @@ [versions] -versionName = "0.6.8" -versionCode = "190" -targetSdk = "34" -compileSdk = "35" -minSdk = "24" agp = "8.9.1" +apacheStringUtils = "3.20.0" +appcompat = "1.7.1" +blurhash = "0.3.0" +catppuccin = "0.1.2" +compileSdk = "36" +composeActivity = "1.12.2" +composeBom = "2025.12.01" +composeCompiler = "1.5.7" +composeGestures = "4.0.0" +composeJunit = "1.10.0" +coreKtx = "1.17.0" +crop = "0.1.1" +crypto = "1.1.0" +dayNightSwitch = "1.0.0" +exif = "1.4.2" +falClient = "0.7.1" +gson = "2.13.2" +junit = "4.13.2" +koin = "4.1.1" +koinCompose = "4.1.1" kotlin = "2.1.20" ksp = "2.1.20-1.0.32" # Should match kotlin version -appcompat = "1.7.0" -composeCompiler = "1.5.7" -composeBom = "2025.03.01" -composeActivity = "1.10.1" -composeJunit = "1.7.8" -lifecycleViewModel = "2.8.7" -navigation = "2.8.9" +lifecycleViewModel = "2.10.0" +material = "1.13.0" +mediaPipeGenerator = "0.10.21" +minSdk = "24" +mockito = "2.2.0" +mockk = "1.14.7" +mvi = "1.0.2" +navigation = "2.9.6" +okhttp = "5.0.0-alpha.14" +onnx = "1.23.2" paging = "3.1.1" # Do not upgrade! pagingCompose = "1.0.0-alpha18" # Do not upgrade! -gson = "2.12.1" -retrofit = "2.11.0" -okhttp = "5.0.0-alpha.14" -koin = "4.0.4" -koinCompose = "4.0.4" +preferences = "1.0.1" +retrofit = "3.0.0" +roboelectric = "4.16" +room = "2.6.1" rxAndroidVersion = "3.0.2" -rxJavaVersion = "3.1.10" +rxJavaVersion = "3.1.12" rxKotlinVersion = "3.0.1" rxNetworkVersion = "4.0.0" -material = "1.12.0" -junit = "4.13.2" -mockk = "1.13.17" -mockito = "2.2.0" -coreKtx = "1.15.0" -crypto = "1.0.0" -onnx = "1.21.0" -workManager = "2.10.0" -room = "2.6.1" +serialization = "1.9.0" +targetSdk = "34" +testCoroutines = "1.10.2" timber = "5.0.1" -exif = "1.4.0" -apacheStringUtils = "3.17.0" -composeGestures = "4.0.0" -crop = "0.1.1" -mvi = "1.0.2" -preferences = "1.0.1" -dayNightSwitch = "1.0.0" -catppuccin = "0.1.2" -turbine = "1.2.0" -roboelectric = "4.14.1" -testCoroutines = "1.10.1" -mediaPipeGenerator = "0.10.21" -serialization = "1.8.1" +turbine = "1.2.1" +versionCode = "192" +versionName = "0.7.1" +workManager = "2.11.0" [libraries] -android-tools-build-gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } -kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } -apache-stringutils = { group = "org.apache.commons", name = "commons-lang3", version.ref = "apacheStringUtils" } -androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } -androidx-compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivity" } -androidx-compose-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewModel" } -androidx-lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewModel" } -androidx-lifecycle-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleViewModel" } -androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } -androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } -androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } -androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } -androidx-compose-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" } -androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } -androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -androidx-exif = { group = "androidx.exifinterface", name = "exifinterface", version.ref = "exif" } -androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" } -androidx-paging-rx = { group = "androidx.paging", name = "paging-rxjava3", version.ref = "paging" } -androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCompose" } -androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "crypto" } -androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } -androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } -androidx-room-rx = { group = "androidx.room", name = "room-rxjava3", version.ref = "room" } -androidx-work-runtime = { group = "androidx.work", name = "work-runtime", version.ref = "workManager" } -google-gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } -google-material = { group = "com.google.android.material", name = "material", version.ref = "material" } -google-mediapipe-image-generator = { group = "com.google.mediapipe", name = "tasks-vision-image-generator", version.ref = "mediaPipeGenerator" } -retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } -retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } -retrofit-adapter-rxjava3 = { group = "com.squareup.retrofit2", name = "adapter-rxjava3", version.ref = "retrofit" } -okhttp-core = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } -okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } -koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" } -koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } -koin-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koinCompose" } -microsoft-onnx-runtime-android = { group = "com.microsoft.onnxruntime", name = "onnxruntime-android", version.ref = "onnx" } -rx-android = { group = "io.reactivex.rxjava3", name = "rxandroid", version.ref = "rxAndroidVersion" } -rx-java = { group = "io.reactivex.rxjava3", name = "rxjava", version.ref = "rxJavaVersion" } -rx-kotlin = { group = "io.reactivex.rxjava3", name = "rxkotlin", version.ref = "rxKotlinVersion" } -rx-network = { group = "io.github.softartdev", name = "reactivenetwork-rx3", version.ref = "rxNetworkVersion" } -timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } -test-junit = { group = "junit", name = "junit", version.ref = "junit" } -test-mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } -test-mockito = { group = "com.nhaarman.mockitokotlin2", name = "mockito-kotlin", version.ref = "mockito" } -test-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "testCoroutines" } -test-koin = { group = "io.insert-koin", name = "koin-test", version.ref = "koin" } -test-koin-junit = { group = "io.insert-koin", name = "koin-test-junit4", version.ref = "koin" } -test-turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } -test-roboelectric = { group = "org.robolectric", name = "robolectric", version.ref = "roboelectric" } -test-compose-junit = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "composeJunit" } -test-compose-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "composeJunit" } -compose-gestures = { group = "com.github.SmartToolFactory", name = "Compose-Extended-Gestures", version.ref = "composeGestures" } -compose-crop = { group = "io.github.mr0xf00", name = "easycrop", version.ref = "crop" } -shifthackz-mvi = { group = "com.github.ShiftHackZ", name = "AndroidCoreMVI", version.ref = "mvi" } -shifthackz-preferences = { group = "com.github.ShiftHackZ", name = "AndroidPreferences", version.ref = "preferences" } -shifthackz-daynightswitch = { group = "com.github.ShiftHackZ", name = "DayNightSwitch", version.ref = "dayNightSwitch" } -shifthackz-catppuccin-legacy = { group = "com.github.ShiftHackZ.Catppuccin-Android-Library", name = "palette-legacy", version.ref = "catppuccin" } -shifthackz-catppuccin-compose = { group = "com.github.ShiftHackZ.Catppuccin-Android-Library", name = "compose", version.ref = "catppuccin" } -shifthackz-catppuccin-splash = { group = "com.github.ShiftHackZ.Catppuccin-Android-Library", name = "splashscreen", version.ref = "catppuccin" } -kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } +android-tools-build-gradle = { module = "com.android.tools.build:gradle", version.ref = "agp" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +androidx-compose-activity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" } +androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } +androidx-compose-material-icons = { module = "androidx.compose.material:material-icons-extended" } +androidx-compose-material3 = { module = "androidx.compose.material3:material3" } +androidx-compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "navigation" } +androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } +androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } +androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } +androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +androidx-compose-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewModel" } +androidx-core = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } +androidx-exif = { module = "androidx.exifinterface:exifinterface", version.ref = "exif" } +androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleViewModel" } +androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewModel" } +androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingCompose" } +androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging" } +androidx-paging-rx = { module = "androidx.paging:paging-rxjava3", version.ref = "paging" } +androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } +androidx-room-rx = { module = "androidx.room:room-rxjava3", version.ref = "room" } +androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "crypto" } +androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "workManager" } +apache-stringutils = { module = "org.apache.commons:commons-lang3", version.ref = "apacheStringUtils" } +blurhash = { module = "com.vanniktech:blurhash", version.ref = "blurhash" } +compose-crop = { module = "io.github.mr0xf00:easycrop", version.ref = "crop" } +compose-gestures = { module = "com.github.SmartToolFactory:Compose-Extended-Gestures", version.ref = "composeGestures" } +fal-client-kotlin = { module = "ai.fal.client:fal-client-kotlin", version.ref = "falClient" } +google-gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +google-material = { module = "com.google.android.material:material", version.ref = "material" } +google-mediapipe-image-generator = { module = "com.google.mediapipe:tasks-vision-image-generator", version.ref = "mediaPipeGenerator" } +koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinCompose" } +koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } +microsoft-onnx-runtime-android = { module = "com.microsoft.onnxruntime:onnxruntime-android", version.ref = "onnx" } +okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } +retrofit-adapter-rxjava3 = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "retrofit" } +retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } +retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +rx-android = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxAndroidVersion" } +rx-java = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxJavaVersion" } +rx-kotlin = { module = "io.reactivex.rxjava3:rxkotlin", version.ref = "rxKotlinVersion" } +rx-network = { module = "io.github.softartdev:reactivenetwork-rx3", version.ref = "rxNetworkVersion" } +shifthackz-catppuccin-compose = { module = "com.github.ShiftHackZ.Catppuccin-Android-Library:compose", version.ref = "catppuccin" } +shifthackz-catppuccin-legacy = { module = "com.github.ShiftHackZ.Catppuccin-Android-Library:palette-legacy", version.ref = "catppuccin" } +shifthackz-catppuccin-splash = { module = "com.github.ShiftHackZ.Catppuccin-Android-Library:splashscreen", version.ref = "catppuccin" } +shifthackz-daynightswitch = { module = "com.github.ShiftHackZ:DayNightSwitch", version.ref = "dayNightSwitch" } +shifthackz-mvi = { module = "com.github.ShiftHackZ:AndroidCoreMVI", version.ref = "mvi" } +shifthackz-preferences = { module = "com.github.ShiftHackZ:AndroidPreferences", version.ref = "preferences" } +test-compose-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "composeJunit" } +test-compose-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "composeJunit" } +kotlinx-coroutines-rx3 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3", version.ref = "testCoroutines" } +test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "testCoroutines" } +test-junit = { module = "junit:junit", version.ref = "junit" } +test-koin = { module = "io.insert-koin:koin-test", version.ref = "koin" } +test-koin-junit = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" } +test-mockito = { module = "com.nhaarman.mockitokotlin2:mockito-kotlin", version.ref = "mockito" } +test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +test-roboelectric = { module = "org.robolectric:robolectric", version.ref = "roboelectric" } +test-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } +timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } androidx-room = { id = "androidx.room", version.ref = "room" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +generic-application = "generic.application:unspecified" +generic-baseline-profm = "generic.baseline.profm:unspecified" +generic-compose = "generic.compose:unspecified" +generic-flavors = "generic.flavors:unspecified" +generic-library = "generic.library:unspecified" +google-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -google-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -generic-flavors = { id = "generic.flavors", version = "unspecified" } -generic-library = { id = "generic.library", version = "unspecified" } -generic-baseline-profm = { id = "generic.baseline.profm", version = "unspecified" } -generic-compose = { id = "generic.compose", version = "unspecified" } -generic-application = { id = "generic.application", version = "unspecified" } -compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/network/build.gradle.kts b/network/build.gradle.kts index 989be25cd..cc733f8e3 100755 --- a/network/build.gradle.kts +++ b/network/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.network" + namespace = "dev.minios.pdaiv1.network" } dependencies { diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/automatic1111/Automatic1111RestApi.kt b/network/src/main/java/com/shifthackz/aisdv1/network/api/automatic1111/Automatic1111RestApi.kt deleted file mode 100755 index 8c7a37588..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/automatic1111/Automatic1111RestApi.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.shifthackz.aisdv1.network.api.automatic1111 - -import com.shifthackz.aisdv1.network.model.ServerConfigurationRaw -import com.shifthackz.aisdv1.network.model.StableDiffusionHyperNetworkRaw -import com.shifthackz.aisdv1.network.model.StableDiffusionLoraRaw -import com.shifthackz.aisdv1.network.model.StableDiffusionModelRaw -import com.shifthackz.aisdv1.network.model.StableDiffusionSamplerRaw -import com.shifthackz.aisdv1.network.request.ImageToImageRequest -import com.shifthackz.aisdv1.network.request.TextToImageRequest -import com.shifthackz.aisdv1.network.response.SdEmbeddingsResponse -import com.shifthackz.aisdv1.network.response.SdGenerationResponse -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Url - -/** - * Main api interface that implements A1111 network REST contracts. - * - * Swagger: http://127.0.0.1:7860/docs - */ -interface Automatic1111RestApi { - - @GET - fun healthCheck(@Url url: String): Completable - - @GET - fun fetchConfiguration(@Url url: String): Single - - @POST - fun updateConfiguration( - @Url url: String, - @Body request: ServerConfigurationRaw, - ): Completable - - @GET - fun fetchSdModels(@Url url: String): Single> - - @GET - fun fetchSamplers(@Url url: String): Single> - - @GET - fun fetchLoras(@Url url: String): Single> - - @GET - fun fetchHyperNetworks(@Url url: String): Single> - - @GET - fun fetchEmbeddings(@Url url: String): Single - - @POST - fun textToImage( - @Url url: String, - @Body request: TextToImageRequest, - ): Single - - @POST - fun imageToImage( - @Url url: String, - @Body request: ImageToImageRequest - ): Single - - @POST - fun interrupt(@Url url: String): Completable - - companion object { - const val PATH_SD_OPTIONS = "sdapi/v1/options" - const val PATH_SD_MODELS = "sdapi/v1/sd-models" - const val PATH_SAMPLERS = "sdapi/v1/samplers" - const val PATH_TXT_TO_IMG = "sdapi/v1/txt2img" - const val PATH_IMG_TO_IMG = "sdapi/v1/img2img" - const val PATH_LORAS = "sdapi/v1/loras" - const val PATH_HYPER_NETWORKS = "sdapi/v1/hypernetworks" - const val PATH_EMBEDDINGS = "sdapi/v1/embeddings" - const val PATH_INTERRUPT = "sdapi/v1/interrupt" - } -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/horde/HordeRestApi.kt b/network/src/main/java/com/shifthackz/aisdv1/network/api/horde/HordeRestApi.kt deleted file mode 100644 index 729e1e53d..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/horde/HordeRestApi.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.shifthackz.aisdv1.network.api.horde - -import com.shifthackz.aisdv1.network.request.HordeGenerationAsyncRequest -import com.shifthackz.aisdv1.network.response.HordeGenerationAsyncResponse -import com.shifthackz.aisdv1.network.response.HordeGenerationCheckFullResponse -import com.shifthackz.aisdv1.network.response.HordeGenerationCheckResponse -import com.shifthackz.aisdv1.network.response.HordeUserResponse -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single -import retrofit2.http.Body -import retrofit2.http.DELETE -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Path - -interface HordeRestApi { - - @POST("/api/v2/generate/async") - fun generateAsync(@Body request: HordeGenerationAsyncRequest): Single - - @GET("/api/v2/generate/check/{id}") - fun checkGeneration(@Path("id") id: String): Single - - @GET("/api/v2/generate/status/{id}") - fun checkStatus(@Path("id") id: String): Single - - @GET("/api/v2/find_user") - fun checkHordeApiKey(): Single - - @DELETE("/api/v2/generate/status/{id}") - fun cancelRequest(@Path("id") requestId: String): Completable -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DonateApi.kt b/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DonateApi.kt deleted file mode 100644 index d8a545f11..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DonateApi.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.network.api.sdai - -import com.shifthackz.aisdv1.network.model.SupporterRaw -import io.reactivex.rxjava3.core.Single -import retrofit2.http.GET - -interface DonateApi { - - @GET("/supporters.json") - fun fetchSupporters(): Single> -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/HuggingFaceModelsApi.kt b/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/HuggingFaceModelsApi.kt deleted file mode 100644 index fd223c027..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/HuggingFaceModelsApi.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.network.api.sdai - -import com.shifthackz.aisdv1.network.model.HuggingFaceModelRaw -import io.reactivex.rxjava3.core.Single -import retrofit2.http.GET - -interface HuggingFaceModelsApi { - - @GET("/hf-models.json") - fun fetchHuggingFaceModels(): Single> -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/ReportApi.kt b/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/ReportApi.kt deleted file mode 100644 index 5c4c60157..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/ReportApi.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.network.api.sdai - -import com.shifthackz.aisdv1.network.request.ReportRequest -import io.reactivex.rxjava3.core.Completable -import retrofit2.http.Body -import retrofit2.http.POST - -interface ReportApi { - - @POST("/report") - fun postReport(@Body request: ReportRequest): Completable -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/di/NetworkModule.kt b/network/src/main/java/com/shifthackz/aisdv1/network/di/NetworkModule.kt deleted file mode 100755 index eb6a3528f..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/di/NetworkModule.kt +++ /dev/null @@ -1,193 +0,0 @@ -package com.shifthackz.aisdv1.network.di - -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.Strictness -import com.shifthackz.aisdv1.network.api.automatic1111.Automatic1111RestApi -import com.shifthackz.aisdv1.network.api.horde.HordeRestApi -import com.shifthackz.aisdv1.network.api.huggingface.HuggingFaceApi -import com.shifthackz.aisdv1.network.api.huggingface.HuggingFaceInferenceApi -import com.shifthackz.aisdv1.network.api.huggingface.HuggingFaceInferenceApiImpl -import com.shifthackz.aisdv1.network.api.imagecdn.ImageCdnRestApi -import com.shifthackz.aisdv1.network.api.imagecdn.ImageCdnRestApiImpl -import com.shifthackz.aisdv1.network.api.openai.OpenAiApi -import com.shifthackz.aisdv1.network.api.sdai.DonateApi -import com.shifthackz.aisdv1.network.api.sdai.DownloadableModelsApi -import com.shifthackz.aisdv1.network.api.sdai.DownloadableModelsApiImpl -import com.shifthackz.aisdv1.network.api.sdai.HuggingFaceModelsApi -import com.shifthackz.aisdv1.network.api.sdai.ReportApi -import com.shifthackz.aisdv1.network.api.stabilityai.StabilityAiApi -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApi -import com.shifthackz.aisdv1.network.api.swarmui.SwarmUiApiImpl -import com.shifthackz.aisdv1.network.authenticator.RestAuthenticator -import com.shifthackz.aisdv1.network.connectivity.ConnectivityMonitor -import com.shifthackz.aisdv1.network.error.StabilityAiErrorMapper -import com.shifthackz.aisdv1.network.extensions.withBaseUrl -import com.shifthackz.aisdv1.network.interceptor.HeaderInterceptor -import com.shifthackz.aisdv1.network.interceptor.LoggingInterceptor -import com.shifthackz.aisdv1.network.qualifiers.ApiUrlProvider -import com.shifthackz.aisdv1.network.qualifiers.HttpInterceptor -import com.shifthackz.aisdv1.network.qualifiers.HttpInterceptors -import com.shifthackz.aisdv1.network.qualifiers.NetworkInterceptor -import com.shifthackz.aisdv1.network.qualifiers.NetworkInterceptors -import com.shifthackz.aisdv1.network.qualifiers.RetrofitCallAdapters -import com.shifthackz.aisdv1.network.qualifiers.RetrofitConverterFactories -import okhttp3.OkHttpClient -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.module -import retrofit2.Retrofit -import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory -import retrofit2.converter.gson.GsonConverterFactory -import java.util.concurrent.TimeUnit - -private const val HTTP_TIMEOUT = 10L - -val networkModule = module { - - single { - GsonBuilder() - .setStrictness(Strictness.LENIENT) - .create() - } - - single { RestAuthenticator(get()) } - - single { - RetrofitConverterFactories( - buildList { - add(GsonConverterFactory.create(get())) - } - ) - } - - single { - RetrofitCallAdapters( - buildList { - add(RxJava3CallAdapterFactory.create()) - } - ) - } - - single { - HttpInterceptors( - listOf( - HttpInterceptor(HeaderInterceptor(get(), get())), - ) - ) - } - - single { - NetworkInterceptors( - listOf( - NetworkInterceptor(LoggingInterceptor().get()), - ) - ) - } - - single { - OkHttpClient - .Builder() - .apply { - get().interceptors.forEach(::addInterceptor) - get().interceptors.forEach(::addNetworkInterceptor) - authenticator(get()) - } - .connectTimeout(HTTP_TIMEOUT, TimeUnit.MINUTES) - .readTimeout(HTTP_TIMEOUT, TimeUnit.MINUTES) - .build() - } - - single { - Retrofit - .Builder() - .apply { - get().data.forEach(::addConverterFactory) - get().data.forEach(::addCallAdapterFactory) - } - .client(get()) - } - - single { - get() - .withBaseUrl(get().stableDiffusionAutomaticApiUrl) - .create(Automatic1111RestApi::class.java) - } - - single { - get() - .withBaseUrl(get().stableDiffusionAutomaticApiUrl) - .create(SwarmUiApi.RawApi::class.java) - } - - single { - get() - .withBaseUrl(get().hordeApiUrl) - .create(HordeRestApi::class.java) - } - - single { - get() - .withBaseUrl(get().stableDiffusionAppApiUrl) - .create(DownloadableModelsApi.RawApi::class.java) - } - - single { - get() - .withBaseUrl(get().stableDiffusionAppApiUrl) - .create(HuggingFaceModelsApi::class.java) - } - - single { - get() - .withBaseUrl(get().stableDiffusionAppApiUrl) - .create(DonateApi::class.java) - } - - single { - get() - .withBaseUrl(get().stableDiffusionReportApiUrl) - .create(ReportApi::class.java) - } - - single { - get() - .withBaseUrl(get().imageCdnApiUrl) - .create(ImageCdnRestApi.RawApi::class.java) - } - - single { - get() - .withBaseUrl(get().huggingFaceInferenceApiUrl) - .create(HuggingFaceInferenceApi.RawApi::class.java) - } - - single { - get() - .withBaseUrl(get().huggingFaceApiUrl) - .create(HuggingFaceApi::class.java) - } - - single { - get() - .withBaseUrl(get().openAiApiUrl) - .create(OpenAiApi::class.java) - } - - single { - get() - .withBaseUrl(get().stabilityAiApiUrl) - .create(StabilityAiApi::class.java) - } - - singleOf(::ImageCdnRestApiImpl) bind ImageCdnRestApi::class - singleOf(::DownloadableModelsApiImpl) bind DownloadableModelsApi::class - singleOf(::HuggingFaceInferenceApiImpl) bind HuggingFaceInferenceApi::class - singleOf(::SwarmUiApiImpl) bind SwarmUiApi::class - - factory { params -> - ConnectivityMonitor(params.get()) - } - - factory { StabilityAiErrorMapper(get()) } -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/exception/BadSessionException.kt b/network/src/main/java/com/shifthackz/aisdv1/network/exception/BadSessionException.kt deleted file mode 100644 index c0589290f..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/exception/BadSessionException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.shifthackz.aisdv1.network.exception - -class BadSessionException : Throwable() diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/extensions/RetrofitExtensions.kt b/network/src/main/java/com/shifthackz/aisdv1/network/extensions/RetrofitExtensions.kt deleted file mode 100755 index 07ec9975d..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/extensions/RetrofitExtensions.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.network.extensions - -import retrofit2.Retrofit - -internal fun Retrofit.Builder.withBaseUrl(baseUrl: String): Retrofit = this - .baseUrl(baseUrl) - .build() diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/interceptor/HeaderInterceptor.kt b/network/src/main/java/com/shifthackz/aisdv1/network/interceptor/HeaderInterceptor.kt deleted file mode 100644 index 422bdca7d..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/interceptor/HeaderInterceptor.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.shifthackz.aisdv1.network.interceptor - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.extensions.applyIf -import com.shifthackz.aisdv1.network.qualifiers.NetworkHeaders -import com.shifthackz.aisdv1.network.qualifiers.ApiKeyProvider -import okhttp3.Interceptor -import okhttp3.Response - -internal class HeaderInterceptor( - private val buildInfoProvider: BuildInfoProvider, - private val apiKeyProvider: ApiKeyProvider, -) : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response = chain - .request() - .newBuilder() - .addHeader(NetworkHeaders.APP_VERSION, buildInfoProvider.version.toString()) - .applyIf(apiKeyProvider() != null) { - val (header, key) = apiKeyProvider.invoke() ?: ("" to "") - addHeader(header, key) - } - .build() - .let(chain::proceed) -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/ApiKeyProvider.kt b/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/ApiKeyProvider.kt deleted file mode 100644 index c77a23b6a..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/ApiKeyProvider.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.network.qualifiers - -fun interface ApiKeyProvider { - operator fun invoke(): Pair? -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkPrefixes.kt b/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkPrefixes.kt deleted file mode 100644 index 7a7fde0cb..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkPrefixes.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.network.qualifiers - -object NetworkPrefixes { - const val BEARER = "Bearer" -} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/RetrofitCallAdapters.kt b/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/RetrofitCallAdapters.kt deleted file mode 100755 index 1a89f5fd3..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/RetrofitCallAdapters.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.network.qualifiers - -import retrofit2.CallAdapter - -internal data class RetrofitCallAdapters(val data: List) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/RetrofitConverterFactories.kt b/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/RetrofitConverterFactories.kt deleted file mode 100755 index fd9db209f..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/RetrofitConverterFactories.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.network.qualifiers - -import retrofit2.Converter - -internal data class RetrofitConverterFactories(val data: List) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/TextToImageRequest.kt b/network/src/main/java/com/shifthackz/aisdv1/network/request/TextToImageRequest.kt deleted file mode 100755 index 7705b7c3e..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/TextToImageRequest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.shifthackz.aisdv1.network.request - -import com.google.gson.annotations.SerializedName - -data class TextToImageRequest( - @SerializedName("prompt") - val prompt: String, - @SerializedName("negative_prompt") - val negativePrompt: String, - @SerializedName("steps") - val steps: Int, - @SerializedName("cfg_scale") - val cfgScale: Float, - @SerializedName("width") - val width: Int, - @SerializedName("height") - val height: Int, - @SerializedName("restore_faces") - val restoreFaces: Boolean, - @SerializedName("seed") - val seed: String?, - @SerializedName("subseed") - val subSeed: String?, - @SerializedName("subseed_strength") - val subSeedStrength: Float?, - @SerializedName("sampler_index") - val samplerIndex: String, -) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/DownloadableModelResponse.kt b/network/src/main/java/com/shifthackz/aisdv1/network/response/DownloadableModelResponse.kt deleted file mode 100644 index 5f08ac66a..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/DownloadableModelResponse.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.network.response - -import com.google.gson.annotations.SerializedName - -data class DownloadableModelResponse( - @SerializedName("id") - val id: String?, - @SerializedName("name") - val name: String?, - @SerializedName("size") - val size: String?, - @SerializedName("sources") - val sources: List?, -) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/HordeUserResponse.kt b/network/src/main/java/com/shifthackz/aisdv1/network/response/HordeUserResponse.kt deleted file mode 100644 index 4da773332..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/HordeUserResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.network.response - -import com.google.gson.annotations.SerializedName - -data class HordeUserResponse( - @SerializedName("id") - val id: Int?, -) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/OpenAiResponse.kt b/network/src/main/java/com/shifthackz/aisdv1/network/response/OpenAiResponse.kt deleted file mode 100644 index 10e29b69b..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/OpenAiResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.network.response - -import com.google.gson.annotations.SerializedName -import com.shifthackz.aisdv1.network.model.OpenAiImageRaw - -data class OpenAiResponse( - @SerializedName("created") - val created: Long?, - @SerializedName("data") - val data: List? -) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiModelsResponse.kt b/network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiModelsResponse.kt deleted file mode 100644 index 4913c5b93..000000000 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiModelsResponse.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.network.response - -import com.google.gson.annotations.SerializedName -import com.shifthackz.aisdv1.network.model.SwarmUiModelRaw - -data class SwarmUiModelsResponse( - @SerializedName("files") - val files: List?, -) diff --git a/network/src/main/java/dev/minios/pdaiv1/network/api/automatic1111/Automatic1111RestApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/automatic1111/Automatic1111RestApi.kt new file mode 100755 index 000000000..1586d787a --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/automatic1111/Automatic1111RestApi.kt @@ -0,0 +1,89 @@ +package dev.minios.pdaiv1.network.api.automatic1111 + +import dev.minios.pdaiv1.network.model.ExtensionRaw +import dev.minios.pdaiv1.network.model.ForgeModuleRaw +import dev.minios.pdaiv1.network.model.ServerConfigurationRaw +import dev.minios.pdaiv1.network.model.StableDiffusionHyperNetworkRaw +import dev.minios.pdaiv1.network.model.StableDiffusionLoraRaw +import dev.minios.pdaiv1.network.model.StableDiffusionModelRaw +import dev.minios.pdaiv1.network.model.StableDiffusionSamplerRaw +import dev.minios.pdaiv1.network.request.ImageToImageRequest +import dev.minios.pdaiv1.network.request.TextToImageRequest +import dev.minios.pdaiv1.network.response.SdEmbeddingsResponse +import dev.minios.pdaiv1.network.response.SdGenerationResponse +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Url + +/** + * Main api interface that implements A1111 network REST contracts. + * + * Swagger: http://127.0.0.1:7860/docs + */ +interface Automatic1111RestApi { + + @GET + fun healthCheck(@Url url: String): Completable + + @GET + fun fetchConfiguration(@Url url: String): Single + + @POST + fun updateConfiguration( + @Url url: String, + @Body request: ServerConfigurationRaw, + ): Completable + + @GET + fun fetchSdModels(@Url url: String): Single> + + @GET + fun fetchSamplers(@Url url: String): Single> + + @GET + fun fetchLoras(@Url url: String): Single> + + @GET + fun fetchHyperNetworks(@Url url: String): Single> + + @GET + fun fetchEmbeddings(@Url url: String): Single + + @POST + fun textToImage( + @Url url: String, + @Body request: TextToImageRequest, + ): Single + + @POST + fun imageToImage( + @Url url: String, + @Body request: ImageToImageRequest + ): Single + + @POST + fun interrupt(@Url url: String): Completable + + @GET + fun fetchExtensions(@Url url: String): Single> + + @GET + fun fetchForgeModules(@Url url: String): Single> + + companion object { + const val PATH_SD_OPTIONS = "sdapi/v1/options" + const val PATH_SD_MODELS = "sdapi/v1/sd-models" + const val PATH_SAMPLERS = "sdapi/v1/samplers" + const val PATH_TXT_TO_IMG = "sdapi/v1/txt2img" + const val PATH_IMG_TO_IMG = "sdapi/v1/img2img" + const val PATH_LORAS = "sdapi/v1/loras" + const val PATH_HYPER_NETWORKS = "sdapi/v1/hypernetworks" + const val PATH_EMBEDDINGS = "sdapi/v1/embeddings" + const val PATH_INTERRUPT = "sdapi/v1/interrupt" + const val PATH_EXTENSIONS = "sdapi/v1/extensions" + const val PATH_SD_MODULES = "sdapi/v1/sd-modules" + } +} diff --git a/network/src/main/java/dev/minios/pdaiv1/network/api/falai/FalAiApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/falai/FalAiApi.kt new file mode 100644 index 000000000..d73d8ed18 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/falai/FalAiApi.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.network.api.falai + +import dev.minios.pdaiv1.network.request.FalAiTextToImageRequest +import dev.minios.pdaiv1.network.response.FalAiQueueResponse +import dev.minios.pdaiv1.network.response.FalAiQueueStatusResponse +import dev.minios.pdaiv1.network.response.FalAiGenerationResponse +import io.reactivex.rxjava3.core.Single +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Url + +interface FalAiApi { + + /** + * Validate the API key by listing models. + * This is a free endpoint that returns 200 OK for valid keys. + */ + @GET("https://api.fal.ai/v1/models") + fun listModels(@retrofit2.http.Query("limit") limit: Int = 1): Single + + /** + * Submit a text-to-image request to the fal.ai queue. + * Uses full URL to avoid path encoding issues with slashes. + */ + @POST + fun submitToQueue( + @Url url: String, + @Body request: FalAiTextToImageRequest, + ): Single + + /** + * Submit a dynamic request to the fal.ai queue. + * Supports any endpoint with dynamic parameters based on OpenAPI schema. + */ + @POST + fun submitDynamicToQueue( + @Url url: String, + @Body request: Map, + ): Single + + /** + * Check the status of a queued request. + * Uses the status_url returned from submitToQueue. + */ + @GET + fun checkStatus(@Url statusUrl: String): Single + + /** + * Fetch the result of a completed request. + * Uses the response_url returned from submitToQueue. + */ + @GET + fun fetchResult(@Url resultUrl: String): Single +} diff --git a/network/src/main/java/dev/minios/pdaiv1/network/api/horde/HordeRestApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/horde/HordeRestApi.kt new file mode 100644 index 000000000..01e7ebd29 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/horde/HordeRestApi.kt @@ -0,0 +1,32 @@ +package dev.minios.pdaiv1.network.api.horde + +import dev.minios.pdaiv1.network.request.HordeGenerationAsyncRequest +import dev.minios.pdaiv1.network.response.HordeGenerationAsyncResponse +import dev.minios.pdaiv1.network.response.HordeGenerationCheckFullResponse +import dev.minios.pdaiv1.network.response.HordeGenerationCheckResponse +import dev.minios.pdaiv1.network.response.HordeUserResponse +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path + +interface HordeRestApi { + + @POST("/api/v2/generate/async") + fun generateAsync(@Body request: HordeGenerationAsyncRequest): Single + + @GET("/api/v2/generate/check/{id}") + fun checkGeneration(@Path("id") id: String): Single + + @GET("/api/v2/generate/status/{id}") + fun checkStatus(@Path("id") id: String): Single + + @GET("/api/v2/find_user") + fun checkHordeApiKey(): Single + + @DELETE("/api/v2/generate/status/{id}") + fun cancelRequest(@Path("id") requestId: String): Completable +} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceApi.kt similarity index 76% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceApi.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceApi.kt index a1821f48c..8a423ad0b 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceApi.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceApi.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.api.huggingface +package dev.minios.pdaiv1.network.api.huggingface import io.reactivex.rxjava3.core.Completable import retrofit2.http.GET diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceInferenceApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceInferenceApi.kt similarity index 83% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceInferenceApi.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceInferenceApi.kt index f18a3bead..7e67a7866 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceInferenceApi.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceInferenceApi.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.network.api.huggingface +package dev.minios.pdaiv1.network.api.huggingface import android.graphics.Bitmap -import com.shifthackz.aisdv1.network.request.HuggingFaceGenerationRequest +import dev.minios.pdaiv1.network.request.HuggingFaceGenerationRequest import io.reactivex.rxjava3.core.Single import okhttp3.ResponseBody import retrofit2.Response diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceInferenceApiImpl.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceInferenceApiImpl.kt similarity index 89% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceInferenceApiImpl.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceInferenceApiImpl.kt index 27b3d2ca0..66c98a818 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/huggingface/HuggingFaceInferenceApiImpl.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/huggingface/HuggingFaceInferenceApiImpl.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.network.api.huggingface +package dev.minios.pdaiv1.network.api.huggingface import android.graphics.Bitmap import android.graphics.BitmapFactory -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.network.request.HuggingFaceGenerationRequest +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.network.request.HuggingFaceGenerationRequest import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import java.util.concurrent.TimeUnit diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/imagecdn/ImageCdnRestApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/imagecdn/ImageCdnRestApi.kt similarity index 87% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/imagecdn/ImageCdnRestApi.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/imagecdn/ImageCdnRestApi.kt index e5c3f61f4..21d0df953 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/imagecdn/ImageCdnRestApi.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/imagecdn/ImageCdnRestApi.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.api.imagecdn +package dev.minios.pdaiv1.network.api.imagecdn import android.graphics.Bitmap import io.reactivex.rxjava3.core.Single diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/imagecdn/ImageCdnRestApiImpl.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/imagecdn/ImageCdnRestApiImpl.kt similarity index 92% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/imagecdn/ImageCdnRestApiImpl.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/imagecdn/ImageCdnRestApiImpl.kt index ae609d9a1..024d5fdb1 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/imagecdn/ImageCdnRestApiImpl.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/imagecdn/ImageCdnRestApiImpl.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.api.imagecdn +package dev.minios.pdaiv1.network.api.imagecdn import android.graphics.Bitmap import android.graphics.BitmapFactory diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/openai/OpenAiApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/openai/OpenAiApi.kt similarity index 82% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/openai/OpenAiApi.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/openai/OpenAiApi.kt index 2635dd2ed..73bf3be18 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/openai/OpenAiApi.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/openai/OpenAiApi.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.network.api.openai +package dev.minios.pdaiv1.network.api.openai -import com.shifthackz.aisdv1.network.request.OpenAiRequest -import com.shifthackz.aisdv1.network.response.OpenAiResponse +import dev.minios.pdaiv1.network.request.OpenAiRequest +import dev.minios.pdaiv1.network.response.OpenAiResponse import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import retrofit2.http.Body diff --git a/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DonateApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DonateApi.kt new file mode 100644 index 000000000..b7de44495 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DonateApi.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.network.api.pdai + +import dev.minios.pdaiv1.network.model.SupporterRaw +import io.reactivex.rxjava3.core.Single +import retrofit2.http.GET + +interface DonateApi { + + @GET("/supporters.json") + fun fetchSupporters(): Single> +} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DownloadableModelsApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DownloadableModelsApi.kt similarity index 77% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DownloadableModelsApi.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DownloadableModelsApi.kt index 78a353822..15e95f716 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DownloadableModelsApi.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DownloadableModelsApi.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.network.api.sdai +package dev.minios.pdaiv1.network.api.pdai -import com.shifthackz.aisdv1.network.response.DownloadableModelResponse +import dev.minios.pdaiv1.network.response.DownloadableModelResponse import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import okhttp3.ResponseBody @@ -15,6 +15,8 @@ interface DownloadableModelsApi { fun fetchMediaPipeModels(): Single> + fun fetchQnnModels(): Single> + fun downloadModel( remoteUrl: String, localPath: String, @@ -30,6 +32,9 @@ interface DownloadableModelsApi { @GET("/mediapipe.json") fun fetchMediaPipeModels(): Single> + @GET("/qnn.json") + fun fetchQnnModels(): Single> + @Streaming @GET fun downloadModel(@Url url: String): Single diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DownloadableModelsApiImpl.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DownloadableModelsApiImpl.kt similarity index 77% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DownloadableModelsApiImpl.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DownloadableModelsApiImpl.kt index 04fd71c10..a42df7f04 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/sdai/DownloadableModelsApiImpl.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/DownloadableModelsApiImpl.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.network.api.sdai +package dev.minios.pdaiv1.network.api.pdai -import com.shifthackz.aisdv1.network.extensions.saveFile -import com.shifthackz.aisdv1.network.response.DownloadableModelResponse +import dev.minios.pdaiv1.network.extensions.saveFile +import dev.minios.pdaiv1.network.response.DownloadableModelResponse import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import java.io.File @@ -14,6 +14,8 @@ internal class DownloadableModelsApiImpl( override fun fetchMediaPipeModels() = rawApi.fetchMediaPipeModels() + override fun fetchQnnModels() = rawApi.fetchQnnModels() + override fun downloadModel( remoteUrl: String, localPath: String, diff --git a/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/HuggingFaceModelsApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/HuggingFaceModelsApi.kt new file mode 100644 index 000000000..37853fc84 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/HuggingFaceModelsApi.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.network.api.pdai + +import dev.minios.pdaiv1.network.model.HuggingFaceModelRaw +import io.reactivex.rxjava3.core.Single +import retrofit2.http.GET + +interface HuggingFaceModelsApi { + + @GET("/hf-models.json") + fun fetchHuggingFaceModels(): Single> +} diff --git a/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/ReportApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/ReportApi.kt new file mode 100644 index 000000000..31005504d --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/pdai/ReportApi.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.network.api.pdai + +import dev.minios.pdaiv1.network.request.ReportRequest +import io.reactivex.rxjava3.core.Completable +import retrofit2.http.Body +import retrofit2.http.POST + +interface ReportApi { + + @POST("/report") + fun postReport(@Body request: ReportRequest): Completable +} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/stabilityai/StabilityAiApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/stabilityai/StabilityAiApi.kt similarity index 83% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/stabilityai/StabilityAiApi.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/stabilityai/StabilityAiApi.kt index 31160bd62..771fc70ba 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/stabilityai/StabilityAiApi.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/stabilityai/StabilityAiApi.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.network.api.stabilityai +package dev.minios.pdaiv1.network.api.stabilityai -import com.shifthackz.aisdv1.network.model.StabilityAiEngineRaw -import com.shifthackz.aisdv1.network.request.StabilityTextToImageRequest -import com.shifthackz.aisdv1.network.response.StabilityCreditsResponse -import com.shifthackz.aisdv1.network.response.StabilityGenerationResponse +import dev.minios.pdaiv1.network.model.StabilityAiEngineRaw +import dev.minios.pdaiv1.network.request.StabilityTextToImageRequest +import dev.minios.pdaiv1.network.response.StabilityCreditsResponse +import dev.minios.pdaiv1.network.response.StabilityGenerationResponse import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import okhttp3.RequestBody diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/swarmui/SwarmUiApi.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/swarmui/SwarmUiApi.kt similarity index 79% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/swarmui/SwarmUiApi.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/swarmui/SwarmUiApi.kt index 0f261edde..e2999be94 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/swarmui/SwarmUiApi.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/swarmui/SwarmUiApi.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.network.api.swarmui +package dev.minios.pdaiv1.network.api.swarmui import android.graphics.Bitmap -import com.shifthackz.aisdv1.network.request.SwarmUiGenerationRequest -import com.shifthackz.aisdv1.network.request.SwarmUiModelsRequest -import com.shifthackz.aisdv1.network.response.SwarmUiGenerationResponse -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse -import com.shifthackz.aisdv1.network.response.SwarmUiSessionResponse +import dev.minios.pdaiv1.network.request.SwarmUiGenerationRequest +import dev.minios.pdaiv1.network.request.SwarmUiModelsRequest +import dev.minios.pdaiv1.network.response.SwarmUiGenerationResponse +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse +import dev.minios.pdaiv1.network.response.SwarmUiSessionResponse import io.reactivex.rxjava3.core.Single import okhttp3.ResponseBody import retrofit2.Response diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/api/swarmui/SwarmUiApiImpl.kt b/network/src/main/java/dev/minios/pdaiv1/network/api/swarmui/SwarmUiApiImpl.kt similarity index 75% rename from network/src/main/java/com/shifthackz/aisdv1/network/api/swarmui/SwarmUiApiImpl.kt rename to network/src/main/java/dev/minios/pdaiv1/network/api/swarmui/SwarmUiApiImpl.kt index 84a94a640..b2accad9e 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/api/swarmui/SwarmUiApiImpl.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/api/swarmui/SwarmUiApiImpl.kt @@ -1,13 +1,13 @@ -package com.shifthackz.aisdv1.network.api.swarmui +package dev.minios.pdaiv1.network.api.swarmui import android.graphics.Bitmap import android.graphics.BitmapFactory -import com.shifthackz.aisdv1.network.exception.BadSessionException -import com.shifthackz.aisdv1.network.request.SwarmUiGenerationRequest -import com.shifthackz.aisdv1.network.request.SwarmUiModelsRequest -import com.shifthackz.aisdv1.network.response.SwarmUiGenerationResponse -import com.shifthackz.aisdv1.network.response.SwarmUiModelsResponse -import com.shifthackz.aisdv1.network.response.SwarmUiSessionResponse +import dev.minios.pdaiv1.network.exception.BadSessionException +import dev.minios.pdaiv1.network.request.SwarmUiGenerationRequest +import dev.minios.pdaiv1.network.request.SwarmUiModelsRequest +import dev.minios.pdaiv1.network.response.SwarmUiGenerationResponse +import dev.minios.pdaiv1.network.response.SwarmUiModelsResponse +import dev.minios.pdaiv1.network.response.SwarmUiSessionResponse import io.reactivex.rxjava3.core.Single import retrofit2.HttpException diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/authenticator/RestAuthenticator.kt b/network/src/main/java/dev/minios/pdaiv1/network/authenticator/RestAuthenticator.kt similarity index 84% rename from network/src/main/java/com/shifthackz/aisdv1/network/authenticator/RestAuthenticator.kt rename to network/src/main/java/dev/minios/pdaiv1/network/authenticator/RestAuthenticator.kt index 1406e011a..fb9194c96 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/authenticator/RestAuthenticator.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/authenticator/RestAuthenticator.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.network.authenticator +package dev.minios.pdaiv1.network.authenticator -import com.shifthackz.aisdv1.network.qualifiers.CredentialsProvider -import com.shifthackz.aisdv1.network.qualifiers.NetworkHeaders +import dev.minios.pdaiv1.network.qualifiers.CredentialsProvider +import dev.minios.pdaiv1.network.qualifiers.NetworkHeaders import okhttp3.Authenticator import okhttp3.Credentials import okhttp3.Request diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/connectivity/ConnectivityMonitor.kt b/network/src/main/java/dev/minios/pdaiv1/network/connectivity/ConnectivityMonitor.kt similarity index 92% rename from network/src/main/java/com/shifthackz/aisdv1/network/connectivity/ConnectivityMonitor.kt rename to network/src/main/java/dev/minios/pdaiv1/network/connectivity/ConnectivityMonitor.kt index 2a36967ee..5d0991611 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/connectivity/ConnectivityMonitor.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/connectivity/ConnectivityMonitor.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.network.connectivity +package dev.minios.pdaiv1.network.connectivity -import com.shifthackz.aisdv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.debugLog import io.reactivex.rxjava3.core.Observable import java.net.HttpURLConnection import java.net.URL diff --git a/network/src/main/java/dev/minios/pdaiv1/network/di/NetworkModule.kt b/network/src/main/java/dev/minios/pdaiv1/network/di/NetworkModule.kt new file mode 100755 index 000000000..9adc8a8d0 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/di/NetworkModule.kt @@ -0,0 +1,200 @@ +package dev.minios.pdaiv1.network.di + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.Strictness +import dev.minios.pdaiv1.network.api.automatic1111.Automatic1111RestApi +import dev.minios.pdaiv1.network.api.falai.FalAiApi +import dev.minios.pdaiv1.network.api.horde.HordeRestApi +import dev.minios.pdaiv1.network.api.huggingface.HuggingFaceApi +import dev.minios.pdaiv1.network.api.huggingface.HuggingFaceInferenceApi +import dev.minios.pdaiv1.network.api.huggingface.HuggingFaceInferenceApiImpl +import dev.minios.pdaiv1.network.api.imagecdn.ImageCdnRestApi +import dev.minios.pdaiv1.network.api.imagecdn.ImageCdnRestApiImpl +import dev.minios.pdaiv1.network.api.openai.OpenAiApi +import dev.minios.pdaiv1.network.api.pdai.DonateApi +import dev.minios.pdaiv1.network.api.pdai.DownloadableModelsApi +import dev.minios.pdaiv1.network.api.pdai.DownloadableModelsApiImpl +import dev.minios.pdaiv1.network.api.pdai.HuggingFaceModelsApi +import dev.minios.pdaiv1.network.api.pdai.ReportApi +import dev.minios.pdaiv1.network.api.stabilityai.StabilityAiApi +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApi +import dev.minios.pdaiv1.network.api.swarmui.SwarmUiApiImpl +import dev.minios.pdaiv1.network.authenticator.RestAuthenticator +import dev.minios.pdaiv1.network.connectivity.ConnectivityMonitor +import dev.minios.pdaiv1.network.error.StabilityAiErrorMapper +import dev.minios.pdaiv1.network.extensions.withBaseUrl +import dev.minios.pdaiv1.network.interceptor.HeaderInterceptor +import dev.minios.pdaiv1.network.interceptor.LoggingInterceptor +import dev.minios.pdaiv1.network.qualifiers.ApiUrlProvider +import dev.minios.pdaiv1.network.qualifiers.HttpInterceptor +import dev.minios.pdaiv1.network.qualifiers.HttpInterceptors +import dev.minios.pdaiv1.network.qualifiers.NetworkInterceptor +import dev.minios.pdaiv1.network.qualifiers.NetworkInterceptors +import dev.minios.pdaiv1.network.qualifiers.RetrofitCallAdapters +import dev.minios.pdaiv1.network.qualifiers.RetrofitConverterFactories +import okhttp3.OkHttpClient +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module +import retrofit2.Retrofit +import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +private const val HTTP_TIMEOUT = 10L + +val networkModule = module { + + single { + GsonBuilder() + .setStrictness(Strictness.LENIENT) + .create() + } + + single { RestAuthenticator(get()) } + + single { + RetrofitConverterFactories( + buildList { + add(GsonConverterFactory.create(get())) + } + ) + } + + single { + RetrofitCallAdapters( + buildList { + add(RxJava3CallAdapterFactory.create()) + } + ) + } + + single { + HttpInterceptors( + listOf( + HttpInterceptor(HeaderInterceptor(get(), get())), + ) + ) + } + + single { + NetworkInterceptors( + listOf( + NetworkInterceptor(LoggingInterceptor().get()), + ) + ) + } + + single { + OkHttpClient + .Builder() + .apply { + get().interceptors.forEach(::addInterceptor) + get().interceptors.forEach(::addNetworkInterceptor) + authenticator(get()) + } + .connectTimeout(HTTP_TIMEOUT, TimeUnit.MINUTES) + .readTimeout(HTTP_TIMEOUT, TimeUnit.MINUTES) + .build() + } + + single { + Retrofit + .Builder() + .apply { + get().data.forEach(::addConverterFactory) + get().data.forEach(::addCallAdapterFactory) + } + .client(get()) + } + + single { + get() + .withBaseUrl(get().stableDiffusionAutomaticApiUrl) + .create(Automatic1111RestApi::class.java) + } + + single { + get() + .withBaseUrl(get().stableDiffusionAutomaticApiUrl) + .create(SwarmUiApi.RawApi::class.java) + } + + single { + get() + .withBaseUrl(get().hordeApiUrl) + .create(HordeRestApi::class.java) + } + + single { + get() + .withBaseUrl(get().stableDiffusionAppApiUrl) + .create(DownloadableModelsApi.RawApi::class.java) + } + + single { + get() + .withBaseUrl(get().stableDiffusionAppApiUrl) + .create(HuggingFaceModelsApi::class.java) + } + + single { + get() + .withBaseUrl(get().stableDiffusionAppApiUrl) + .create(DonateApi::class.java) + } + + single { + get() + .withBaseUrl(get().stableDiffusionReportApiUrl) + .create(ReportApi::class.java) + } + + single { + get() + .withBaseUrl(get().imageCdnApiUrl) + .create(ImageCdnRestApi.RawApi::class.java) + } + + single { + get() + .withBaseUrl(get().huggingFaceInferenceApiUrl) + .create(HuggingFaceInferenceApi.RawApi::class.java) + } + + single { + get() + .withBaseUrl(get().huggingFaceApiUrl) + .create(HuggingFaceApi::class.java) + } + + single { + get() + .withBaseUrl(get().openAiApiUrl) + .create(OpenAiApi::class.java) + } + + single { + get() + .withBaseUrl(get().stabilityAiApiUrl) + .create(StabilityAiApi::class.java) + } + + single { + get() + .withBaseUrl(get().falAiApiUrl) + .create(FalAiApi::class.java) + } + + singleOf(::ImageCdnRestApiImpl) bind ImageCdnRestApi::class + singleOf(::DownloadableModelsApiImpl) bind DownloadableModelsApi::class + singleOf(::HuggingFaceInferenceApiImpl) bind HuggingFaceInferenceApi::class + singleOf(::SwarmUiApiImpl) bind SwarmUiApi::class + + factory { params -> + ConnectivityMonitor(params.get()) + } + + factory { StabilityAiErrorMapper(get()) } +} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/error/StabilityAiErrorMapper.kt b/network/src/main/java/dev/minios/pdaiv1/network/error/StabilityAiErrorMapper.kt similarity index 83% rename from network/src/main/java/com/shifthackz/aisdv1/network/error/StabilityAiErrorMapper.kt rename to network/src/main/java/dev/minios/pdaiv1/network/error/StabilityAiErrorMapper.kt index f42a334fb..ec57a8ebf 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/error/StabilityAiErrorMapper.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/error/StabilityAiErrorMapper.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.network.error +package dev.minios.pdaiv1.network.error import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import com.shifthackz.aisdv1.network.response.StabilityAiErrorResponse +import dev.minios.pdaiv1.network.response.StabilityAiErrorResponse import io.reactivex.rxjava3.core.Single import retrofit2.HttpException diff --git a/network/src/main/java/dev/minios/pdaiv1/network/exception/BadSessionException.kt b/network/src/main/java/dev/minios/pdaiv1/network/exception/BadSessionException.kt new file mode 100644 index 000000000..f681a89c9 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/exception/BadSessionException.kt @@ -0,0 +1,3 @@ +package dev.minios.pdaiv1.network.exception + +class BadSessionException : Throwable() diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/extensions/ResponseBodyExtensions.kt b/network/src/main/java/dev/minios/pdaiv1/network/extensions/ResponseBodyExtensions.kt similarity index 95% rename from network/src/main/java/com/shifthackz/aisdv1/network/extensions/ResponseBodyExtensions.kt rename to network/src/main/java/dev/minios/pdaiv1/network/extensions/ResponseBodyExtensions.kt index 170221f5a..7009614e4 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/extensions/ResponseBodyExtensions.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/extensions/ResponseBodyExtensions.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.extensions +package dev.minios.pdaiv1.network.extensions import io.reactivex.rxjava3.core.Observable import okhttp3.ResponseBody diff --git a/network/src/main/java/dev/minios/pdaiv1/network/extensions/RetrofitExtensions.kt b/network/src/main/java/dev/minios/pdaiv1/network/extensions/RetrofitExtensions.kt new file mode 100755 index 000000000..01bd7e650 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/extensions/RetrofitExtensions.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.network.extensions + +import retrofit2.Retrofit + +internal fun Retrofit.Builder.withBaseUrl(baseUrl: String): Retrofit = this + .baseUrl(baseUrl) + .build() diff --git a/network/src/main/java/dev/minios/pdaiv1/network/interceptor/HeaderInterceptor.kt b/network/src/main/java/dev/minios/pdaiv1/network/interceptor/HeaderInterceptor.kt new file mode 100644 index 000000000..edf2a716e --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/interceptor/HeaderInterceptor.kt @@ -0,0 +1,30 @@ +package dev.minios.pdaiv1.network.interceptor + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.extensions.applyIf +import dev.minios.pdaiv1.network.qualifiers.NetworkHeaders +import dev.minios.pdaiv1.network.qualifiers.ApiKeyProvider +import okhttp3.Interceptor +import okhttp3.Response + +internal class HeaderInterceptor( + private val buildInfoProvider: BuildInfoProvider, + private val apiKeyProvider: ApiKeyProvider, +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response = chain + .request() + .newBuilder() + .addHeader(NetworkHeaders.APP_VERSION, buildInfoProvider.version.toString()) + .applyIf(apiKeyProvider() != null) { + val (header, key) = apiKeyProvider.invoke() ?: ("" to "") + // Sanitize key - remove control characters that OkHttp doesn't allow + val sanitizedKey = key.filter { it.code >= 0x20 || it == '\t' } + if (header.isNotEmpty() && sanitizedKey.isNotEmpty()) { + addHeader(header, sanitizedKey) + } + this + } + .build() + .let(chain::proceed) +} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/interceptor/LoggingInterceptor.kt b/network/src/main/java/dev/minios/pdaiv1/network/interceptor/LoggingInterceptor.kt similarity index 75% rename from network/src/main/java/com/shifthackz/aisdv1/network/interceptor/LoggingInterceptor.kt rename to network/src/main/java/dev/minios/pdaiv1/network/interceptor/LoggingInterceptor.kt index e138632e7..e377334cf 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/interceptor/LoggingInterceptor.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/interceptor/LoggingInterceptor.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.network.interceptor +package dev.minios.pdaiv1.network.interceptor -import com.shifthackz.aisdv1.core.common.log.debugLog +import dev.minios.pdaiv1.core.common.log.debugLog import okhttp3.logging.HttpLoggingInterceptor internal class LoggingInterceptor { diff --git a/network/src/main/java/dev/minios/pdaiv1/network/model/ExtensionRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/ExtensionRaw.kt new file mode 100644 index 000000000..98c779b44 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/ExtensionRaw.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.network.model + +import com.google.gson.annotations.SerializedName + +data class ExtensionRaw( + @SerializedName("name") + val name: String, + @SerializedName("enabled") + val enabled: Boolean, +) diff --git a/network/src/main/java/dev/minios/pdaiv1/network/model/ForgeModuleRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/ForgeModuleRaw.kt new file mode 100644 index 000000000..e45ff1beb --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/ForgeModuleRaw.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.network.model + +import com.google.gson.annotations.SerializedName + +data class ForgeModuleRaw( + @SerializedName("model_name") + val modelName: String?, + @SerializedName("filename") + val filename: String?, +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/HuggingFaceModelRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/HuggingFaceModelRaw.kt similarity index 86% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/HuggingFaceModelRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/HuggingFaceModelRaw.kt index 58532f7ec..a31eb1276 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/HuggingFaceModelRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/HuggingFaceModelRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/OpenAiImageRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/OpenAiImageRaw.kt similarity index 89% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/OpenAiImageRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/OpenAiImageRaw.kt index 547cf06b1..6448f8613 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/OpenAiImageRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/OpenAiImageRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/ServerConfigurationRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/ServerConfigurationRaw.kt similarity index 79% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/ServerConfigurationRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/ServerConfigurationRaw.kt index 108712ae9..89261d24b 100755 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/ServerConfigurationRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/ServerConfigurationRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/StabilityAiEngineRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/StabilityAiEngineRaw.kt similarity index 87% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/StabilityAiEngineRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/StabilityAiEngineRaw.kt index d3febbee1..f14e65f98 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/StabilityAiEngineRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/StabilityAiEngineRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/StabilityTextPromptRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/StabilityTextPromptRaw.kt similarity index 81% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/StabilityTextPromptRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/StabilityTextPromptRaw.kt index ed2cd8499..87cb3b0bb 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/StabilityTextPromptRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/StabilityTextPromptRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionHyperNetworkRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionHyperNetworkRaw.kt similarity index 81% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionHyperNetworkRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionHyperNetworkRaw.kt index 088412c4f..e8de2a46d 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionHyperNetworkRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionHyperNetworkRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionLoraRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionLoraRaw.kt similarity index 84% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionLoraRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionLoraRaw.kt index d6da59147..e5d3aaf06 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionLoraRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionLoraRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionModelRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionModelRaw.kt similarity index 90% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionModelRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionModelRaw.kt index 445fc36c6..2f8c8c6d8 100755 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionModelRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionModelRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionSamplerRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionSamplerRaw.kt similarity index 86% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionSamplerRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionSamplerRaw.kt index 0eb4e90e0..ce7a645af 100755 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/StableDiffusionSamplerRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/StableDiffusionSamplerRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/SupporterRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/SupporterRaw.kt similarity index 91% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/SupporterRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/SupporterRaw.kt index 2ae5fb612..ccf50e36c 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/SupporterRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/SupporterRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/model/SwarmUiModelRaw.kt b/network/src/main/java/dev/minios/pdaiv1/network/model/SwarmUiModelRaw.kt similarity index 84% rename from network/src/main/java/com/shifthackz/aisdv1/network/model/SwarmUiModelRaw.kt rename to network/src/main/java/dev/minios/pdaiv1/network/model/SwarmUiModelRaw.kt index 47f5fc531..62b55c658 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/model/SwarmUiModelRaw.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/model/SwarmUiModelRaw.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.model +package dev.minios.pdaiv1.network.model import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/ApiKeyProvider.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/ApiKeyProvider.kt new file mode 100644 index 000000000..b8f5c1ec9 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/ApiKeyProvider.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.network.qualifiers + +fun interface ApiKeyProvider { + operator fun invoke(): Pair? +} diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/ApiUrlProvider.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/ApiUrlProvider.kt similarity index 83% rename from network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/ApiUrlProvider.kt rename to network/src/main/java/dev/minios/pdaiv1/network/qualifiers/ApiUrlProvider.kt index 90fd00beb..fd0e3e48f 100755 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/ApiUrlProvider.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/ApiUrlProvider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.qualifiers +package dev.minios.pdaiv1.network.qualifiers interface ApiUrlProvider { val stableDiffusionAutomaticApiUrl: String @@ -10,4 +10,5 @@ interface ApiUrlProvider { val huggingFaceInferenceApiUrl: String val openAiApiUrl: String val stabilityAiApiUrl: String + val falAiApiUrl: String } diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/CredentialsProvider.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/CredentialsProvider.kt similarity index 81% rename from network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/CredentialsProvider.kt rename to network/src/main/java/dev/minios/pdaiv1/network/qualifiers/CredentialsProvider.kt index 14179f281..411855d85 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/CredentialsProvider.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/CredentialsProvider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.qualifiers +package dev.minios.pdaiv1.network.qualifiers interface CredentialsProvider { operator fun invoke(): Data diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/HttpInterceptor.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/HttpInterceptor.kt similarity index 85% rename from network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/HttpInterceptor.kt rename to network/src/main/java/dev/minios/pdaiv1/network/qualifiers/HttpInterceptor.kt index 1ddb6a4dc..bf900707a 100755 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/HttpInterceptor.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/HttpInterceptor.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.qualifiers +package dev.minios.pdaiv1.network.qualifiers import okhttp3.Interceptor diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkHeaders.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkHeaders.kt similarity index 75% rename from network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkHeaders.kt rename to network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkHeaders.kt index 853d90cea..ef38d73e5 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkHeaders.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkHeaders.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.qualifiers +package dev.minios.pdaiv1.network.qualifiers object NetworkHeaders { const val APP_VERSION = "X-App-Version" diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkInterceptor.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkInterceptor.kt similarity index 86% rename from network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkInterceptor.kt rename to network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkInterceptor.kt index bd1a015f8..a1353f258 100755 --- a/network/src/main/java/com/shifthackz/aisdv1/network/qualifiers/NetworkInterceptor.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkInterceptor.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.qualifiers +package dev.minios.pdaiv1.network.qualifiers import okhttp3.Interceptor diff --git a/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkPrefixes.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkPrefixes.kt new file mode 100644 index 000000000..d0dcb53f1 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/NetworkPrefixes.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.network.qualifiers + +object NetworkPrefixes { + const val BEARER = "Bearer" + const val KEY = "Key" +} diff --git a/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/RetrofitCallAdapters.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/RetrofitCallAdapters.kt new file mode 100755 index 000000000..730d3acea --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/RetrofitCallAdapters.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.network.qualifiers + +import retrofit2.CallAdapter + +internal data class RetrofitCallAdapters(val data: List) diff --git a/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/RetrofitConverterFactories.kt b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/RetrofitConverterFactories.kt new file mode 100755 index 000000000..e6a185940 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/qualifiers/RetrofitConverterFactories.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.network.qualifiers + +import retrofit2.Converter + +internal data class RetrofitConverterFactories(val data: List) diff --git a/network/src/main/java/dev/minios/pdaiv1/network/request/FalAiTextToImageRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/FalAiTextToImageRequest.kt new file mode 100644 index 000000000..0ae88f96c --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/FalAiTextToImageRequest.kt @@ -0,0 +1,43 @@ +package dev.minios.pdaiv1.network.request + +import com.google.gson.annotations.SerializedName + +data class FalAiTextToImageRequest( + @SerializedName("prompt") + val prompt: String, + + @SerializedName("negative_prompt") + val negativePrompt: String? = null, + + @SerializedName("image_size") + val imageSize: FalAiImageSize? = null, + + @SerializedName("num_inference_steps") + val numInferenceSteps: Int? = null, + + @SerializedName("guidance_scale") + val guidanceScale: Float? = null, + + @SerializedName("seed") + val seed: Long? = null, + + @SerializedName("num_images") + val numImages: Int = 1, + + @SerializedName("output_format") + val outputFormat: String = "jpeg", + + @SerializedName("enable_safety_checker") + val enableSafetyChecker: Boolean = false, + + @SerializedName("sync_mode") + val syncMode: Boolean = false, +) + +data class FalAiImageSize( + @SerializedName("width") + val width: Int, + + @SerializedName("height") + val height: Int, +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/HordeGenerationAsyncRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/HordeGenerationAsyncRequest.kt similarity index 94% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/HordeGenerationAsyncRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/HordeGenerationAsyncRequest.kt index 1a8d5d73b..a8fca534c 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/HordeGenerationAsyncRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/HordeGenerationAsyncRequest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/HuggingFaceGenerationRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/HuggingFaceGenerationRequest.kt similarity index 82% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/HuggingFaceGenerationRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/HuggingFaceGenerationRequest.kt index 645bf75a1..383a08c1d 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/HuggingFaceGenerationRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/HuggingFaceGenerationRequest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/ImageToImageRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/ImageToImageRequest.kt similarity index 87% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/ImageToImageRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/ImageToImageRequest.kt index f09f94551..bc599aa8c 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/ImageToImageRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/ImageToImageRequest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName @@ -43,4 +43,8 @@ data class ImageToImageRequest( val subSeedStrength: Float?, @SerializedName("sampler_index") val samplerIndex: String, + @SerializedName("scheduler") + val scheduler: String? = null, + @SerializedName("alwayson_scripts") + val alwaysOnScripts: Map? = null, ) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/OpenAiRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/OpenAiRequest.kt similarity index 90% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/OpenAiRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/OpenAiRequest.kt index 470742708..b912cea23 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/OpenAiRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/OpenAiRequest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/dev/minios/pdaiv1/network/request/OverrideSettings.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/OverrideSettings.kt new file mode 100644 index 000000000..3cebafc30 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/OverrideSettings.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.network.request + +import com.google.gson.annotations.SerializedName + +data class OverrideSettings( + @SerializedName("forge_additional_modules") + val forgeAdditionalModules: List? = null, +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/ReportRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/ReportRequest.kt similarity index 88% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/ReportRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/ReportRequest.kt index 0d5fecba8..37ea9987f 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/ReportRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/ReportRequest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/StabilityTextToImageRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/StabilityTextToImageRequest.kt similarity index 85% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/StabilityTextToImageRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/StabilityTextToImageRequest.kt index da77bb24f..f48ebdfca 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/StabilityTextToImageRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/StabilityTextToImageRequest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName -import com.shifthackz.aisdv1.network.model.StabilityTextPromptRaw +import dev.minios.pdaiv1.network.model.StabilityTextPromptRaw data class StabilityTextToImageRequest( @SerializedName("height") diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/SwarmUiGenerationRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/SwarmUiGenerationRequest.kt similarity index 96% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/SwarmUiGenerationRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/SwarmUiGenerationRequest.kt index 7bd6f84cc..d54b590a4 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/SwarmUiGenerationRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/SwarmUiGenerationRequest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/request/SwarmUiModelsRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/SwarmUiModelsRequest.kt similarity index 86% rename from network/src/main/java/com/shifthackz/aisdv1/network/request/SwarmUiModelsRequest.kt rename to network/src/main/java/dev/minios/pdaiv1/network/request/SwarmUiModelsRequest.kt index 35461f675..b39653372 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/request/SwarmUiModelsRequest.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/SwarmUiModelsRequest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.request +package dev.minios.pdaiv1.network.request import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/dev/minios/pdaiv1/network/request/TextToImageRequest.kt b/network/src/main/java/dev/minios/pdaiv1/network/request/TextToImageRequest.kt new file mode 100755 index 000000000..8ffba7409 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/request/TextToImageRequest.kt @@ -0,0 +1,52 @@ +package dev.minios.pdaiv1.network.request + +import com.google.gson.annotations.SerializedName + +data class TextToImageRequest( + @SerializedName("prompt") + val prompt: String, + @SerializedName("negative_prompt") + val negativePrompt: String, + @SerializedName("steps") + val steps: Int, + @SerializedName("cfg_scale") + val cfgScale: Float, + @SerializedName("distilled_cfg_scale") + val distilledCfgScale: Float? = null, + @SerializedName("width") + val width: Int, + @SerializedName("height") + val height: Int, + @SerializedName("restore_faces") + val restoreFaces: Boolean, + @SerializedName("seed") + val seed: String?, + @SerializedName("subseed") + val subSeed: String?, + @SerializedName("subseed_strength") + val subSeedStrength: Float?, + @SerializedName("sampler_index") + val samplerIndex: String, + @SerializedName("scheduler") + val scheduler: String? = null, + @SerializedName("alwayson_scripts") + val alwaysOnScripts: Map? = null, + @SerializedName("enable_hr") + val enableHr: Boolean? = null, + @SerializedName("hr_upscaler") + val hrUpscaler: String? = null, + @SerializedName("hr_scale") + val hrScale: Float? = null, + @SerializedName("hr_second_pass_steps") + val hrSecondPassSteps: Int? = null, + @SerializedName("hr_cfg") + val hrCfg: Float? = null, + @SerializedName("hr_distilled_cfg") + val hrDistilledCfg: Float? = null, + @SerializedName("hr_additional_modules") + val hrAdditionalModules: List? = null, + @SerializedName("denoising_strength") + val denoisingStrength: Float? = null, + @SerializedName("override_settings") + val overrideSettings: OverrideSettings? = null, +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/AppVersionResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/AppVersionResponse.kt similarity index 75% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/AppVersionResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/AppVersionResponse.kt index 04d0a6fa6..5755a1534 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/AppVersionResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/AppVersionResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/CoinsResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/CoinsResponse.kt similarity index 75% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/CoinsResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/CoinsResponse.kt index 944aa0dbe..82dba2cd5 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/CoinsResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/CoinsResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/dev/minios/pdaiv1/network/response/DownloadableModelResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/DownloadableModelResponse.kt new file mode 100644 index 000000000..d60ae7d48 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/DownloadableModelResponse.kt @@ -0,0 +1,25 @@ +package dev.minios.pdaiv1.network.response + +import com.google.gson.annotations.SerializedName + +data class DownloadableModelResponse( + @SerializedName("id") + val id: String?, + @SerializedName("name") + val name: String?, + @SerializedName("size") + val size: String?, + @SerializedName("sources") + val sources: List?, + @SerializedName("metadata") + val metadata: ModelMetadata? = null, +) + +data class ModelMetadata( + @SerializedName("chipset") + val chipset: String? = null, + @SerializedName("type") + val type: String? = null, + @SerializedName("src") + val src: String? = null, +) diff --git a/network/src/main/java/dev/minios/pdaiv1/network/response/FalAiResponses.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/FalAiResponses.kt new file mode 100644 index 000000000..55493343c --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/FalAiResponses.kt @@ -0,0 +1,96 @@ +package dev.minios.pdaiv1.network.response + +import com.google.gson.annotations.SerializedName + +/** + * Response from submitting a request to the fal.ai queue. + */ +data class FalAiQueueResponse( + @SerializedName("request_id") + val requestId: String? = null, + + @SerializedName("status") + val status: String? = null, + + @SerializedName("response_url") + val responseUrl: String? = null, + + @SerializedName("status_url") + val statusUrl: String? = null, + + @SerializedName("cancel_url") + val cancelUrl: String? = null, + + // If sync_mode=true or immediate response, images may be included directly + @SerializedName("images") + val images: List? = null, + + // For video generation endpoints + @SerializedName("video") + val video: FalAiImage? = null, + + // Seed can exceed Long.MAX_VALUE, so we use String + @SerializedName("seed") + val seed: String? = null, + + @SerializedName("prompt") + val prompt: String? = null, +) + +/** + * Response when checking status of a queued request. + */ +data class FalAiQueueStatusResponse( + @SerializedName("status") + val status: String, + + @SerializedName("request_id") + val requestId: String? = null, + + @SerializedName("response_url") + val responseUrl: String? = null, + + @SerializedName("queue_position") + val queuePosition: Int? = null, + + @SerializedName("logs") + val logs: Any? = null, +) + +/** + * Final response with generated images or video. + */ +data class FalAiGenerationResponse( + @SerializedName("images") + val images: List? = null, + + @SerializedName("video") + val video: FalAiImage? = null, + + // Seed can exceed Long.MAX_VALUE, so we use String + @SerializedName("seed") + val seed: String? = null, + + @SerializedName("prompt") + val prompt: String? = null, + + @SerializedName("timings") + val timings: Map? = null, + + @SerializedName("has_nsfw_concepts") + val hasNsfwConcepts: List? = null, +) + +data class FalAiImage( + @SerializedName("url") + val url: String, + + @SerializedName("width") + val width: Int? = null, + + @SerializedName("height") + val height: Int? = null, + + @SerializedName("content_type") + val contentType: String? = null, +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/HordeGenerationAsyncResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/HordeGenerationAsyncResponse.kt similarity index 84% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/HordeGenerationAsyncResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/HordeGenerationAsyncResponse.kt index aec7534de..3eef27def 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/HordeGenerationAsyncResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/HordeGenerationAsyncResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/HordeGenerationCheckResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/HordeGenerationCheckResponse.kt similarity index 95% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/HordeGenerationCheckResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/HordeGenerationCheckResponse.kt index 61a4fdd0c..4049fa609 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/HordeGenerationCheckResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/HordeGenerationCheckResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/dev/minios/pdaiv1/network/response/HordeUserResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/HordeUserResponse.kt new file mode 100644 index 000000000..c86ddd66b --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/HordeUserResponse.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.network.response + +import com.google.gson.annotations.SerializedName + +data class HordeUserResponse( + @SerializedName("id") + val id: Int?, +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/HuggingFaceErrorResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/HuggingFaceErrorResponse.kt similarity index 81% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/HuggingFaceErrorResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/HuggingFaceErrorResponse.kt index 1974dcd42..801bcce43 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/HuggingFaceErrorResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/HuggingFaceErrorResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/dev/minios/pdaiv1/network/response/OpenAiResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/OpenAiResponse.kt new file mode 100644 index 000000000..70d0c02c7 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/OpenAiResponse.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.network.response + +import com.google.gson.annotations.SerializedName +import dev.minios.pdaiv1.network.model.OpenAiImageRaw + +data class OpenAiResponse( + @SerializedName("created") + val created: Long?, + @SerializedName("data") + val data: List? +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/SdEmbeddingsResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/SdEmbeddingsResponse.kt similarity index 76% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/SdEmbeddingsResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/SdEmbeddingsResponse.kt index 89d617c1c..30834f199 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/SdEmbeddingsResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/SdEmbeddingsResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/SdGenerationResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/SdGenerationResponse.kt similarity index 90% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/SdGenerationResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/SdGenerationResponse.kt index 7fdcfa522..2b900755b 100755 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/SdGenerationResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/SdGenerationResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityAiErrorResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/StabilityAiErrorResponse.kt similarity index 83% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityAiErrorResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/StabilityAiErrorResponse.kt index b4457d991..deadb032d 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityAiErrorResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/StabilityAiErrorResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityCreditsResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/StabilityCreditsResponse.kt similarity index 75% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityCreditsResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/StabilityCreditsResponse.kt index 107ef1eea..34e31025f 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityCreditsResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/StabilityCreditsResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityGenerationResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/StabilityGenerationResponse.kt similarity index 89% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityGenerationResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/StabilityGenerationResponse.kt index 5f1f58b74..6323fb6b6 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/StabilityGenerationResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/StabilityGenerationResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiGenerationResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiGenerationResponse.kt similarity index 76% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiGenerationResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiGenerationResponse.kt index ae8cdd12c..5b8a18909 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiGenerationResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiGenerationResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiModelsResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiModelsResponse.kt new file mode 100644 index 000000000..fdb69ff24 --- /dev/null +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiModelsResponse.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.network.response + +import com.google.gson.annotations.SerializedName +import dev.minios.pdaiv1.network.model.SwarmUiModelRaw + +data class SwarmUiModelsResponse( + @SerializedName("files") + val files: List?, +) diff --git a/network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiSessionResponse.kt b/network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiSessionResponse.kt similarity index 76% rename from network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiSessionResponse.kt rename to network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiSessionResponse.kt index 8ec5052b6..5e49aab71 100644 --- a/network/src/main/java/com/shifthackz/aisdv1/network/response/SwarmUiSessionResponse.kt +++ b/network/src/main/java/dev/minios/pdaiv1/network/response/SwarmUiSessionResponse.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.network.response +package dev.minios.pdaiv1.network.response import com.google.gson.annotations.SerializedName diff --git a/network/src/test/java/com/shifthackz/aisdv1/network/authenticator/RestAuthenticatorTest.kt b/network/src/test/java/dev/minios/pdaiv1/network/authenticator/RestAuthenticatorTest.kt similarity index 93% rename from network/src/test/java/com/shifthackz/aisdv1/network/authenticator/RestAuthenticatorTest.kt rename to network/src/test/java/dev/minios/pdaiv1/network/authenticator/RestAuthenticatorTest.kt index cf500c13d..098869c2c 100644 --- a/network/src/test/java/com/shifthackz/aisdv1/network/authenticator/RestAuthenticatorTest.kt +++ b/network/src/test/java/dev/minios/pdaiv1/network/authenticator/RestAuthenticatorTest.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.network.authenticator +package dev.minios.pdaiv1.network.authenticator -import com.shifthackz.aisdv1.network.qualifiers.CredentialsProvider -import com.shifthackz.aisdv1.network.qualifiers.NetworkHeaders +import dev.minios.pdaiv1.network.qualifiers.CredentialsProvider +import dev.minios.pdaiv1.network.qualifiers.NetworkHeaders import io.mockk.every import io.mockk.mockk import okhttp3.Address diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 4fec26cbe..1bb9c35da 100755 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.presentation" + namespace = "dev.minios.pdaiv1.presentation" testOptions.unitTests{ all { test -> test.jvmArgs( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionViewModel.kt deleted file mode 100644 index c4b764fd9..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionViewModel.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.shifthackz.aisdv1.presentation.activity - -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.graph.mainDrawerNavItems -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.home.HomeRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import io.reactivex.rxjava3.kotlin.subscribeBy - -class AiStableDiffusionViewModel( - dispatchersProvider: DispatchersProvider, - schedulersProvider: SchedulersProvider, - mainRouter: MainRouter, - drawerRouter: DrawerRouter, - private val homeRouter: HomeRouter, - private val preferenceManager: PreferenceManager, -) : MviRxViewModel() { - - override val initialState = AppState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !mainRouter.observe() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) - - !drawerRouter.observe() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) - - !homeRouter.observe() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) - - !preferenceManager.observe() - .map(::mainDrawerNavItems) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda) { drawerItems -> - updateState { state -> - state.copy(drawerItems = drawerItems) - } - } - } - - override fun processIntent(intent: AppIntent) = when (intent) { - AppIntent.GrantStoragePermission -> { - preferenceManager.saveToMediaStore = true - } - - is AppIntent.HomeRoute -> { - homeRouter.navigateToRoute(intent.navRoute) - } - - AppIntent.HideSplash -> updateState { state -> - state.copy(isShowSplash = false) - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AppIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AppIntent.kt deleted file mode 100644 index 75a02fe39..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AppIntent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.presentation.activity - -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface AppIntent : MviIntent { - - data object GrantStoragePermission : AppIntent - data object HideSplash : AppIntent - - data class HomeRoute(val navRoute: NavigationRoute) : AppIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AppState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AppState.kt deleted file mode 100644 index 83cd15cb3..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AppState.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.presentation.activity - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.presentation.model.NavItem -import com.shifthackz.aisdv1.presentation.navigation.graph.mainDrawerNavItems -import com.shifthackz.android.core.mvi.MviState - -@Immutable -data class AppState( - val drawerItems: List = mainDrawerNavItems(), - val isShowSplash: Boolean = true, -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationFormUpdateEvent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationFormUpdateEvent.kt deleted file mode 100644 index fa9ec3875..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationFormUpdateEvent.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.shifthackz.aisdv1.presentation.core - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.subjects.BehaviorSubject -import io.reactivex.rxjava3.subjects.PublishSubject - -class GenerationFormUpdateEvent { - - private val sRoute: PublishSubject = PublishSubject.create() - private val sTxt2Img: BehaviorSubject = BehaviorSubject.createDefault(Payload.None) - private val sImg2Img: BehaviorSubject = BehaviorSubject.createDefault(Payload.None) - - fun update( - generation: AiGenerationResult, - route: AiGenerationResult.Type, - inputImage: Boolean, - ) { - sRoute.onNext(route) - when (route) { - AiGenerationResult.Type.TEXT_TO_IMAGE -> sTxt2Img.onNext(Payload.T2IForm(generation)) - AiGenerationResult.Type.IMAGE_TO_IMAGE -> sImg2Img.onNext(Payload.I2IForm(generation, inputImage)) - } - } - - fun clear() { - sTxt2Img.onNext(Payload.None) - sImg2Img.onNext(Payload.None) - } - - fun observeRoute() = sRoute.toFlowable(BackpressureStrategy.LATEST) - - fun observeTxt2ImgForm() = sTxt2Img.toFlowable(BackpressureStrategy.LATEST) - - fun observeImg2ImgForm() = sImg2Img.toFlowable(BackpressureStrategy.LATEST) - - sealed interface Payload { - data object None : Payload - data class T2IForm(val ai: AiGenerationResult): Payload - data class I2IForm(val ai: AiGenerationResult, val inputImage: Boolean): Payload - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviIntent.kt deleted file mode 100644 index 8adef7d41..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviIntent.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.shifthackz.aisdv1.presentation.core - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.OpenAiQuality -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.OpenAiStyle -import com.shifthackz.aisdv1.domain.entity.StabilityAiClipGuidance -import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface GenerationMviIntent : MviIntent { - - data class NewPrompts( - val positive: String, - val negative: String, - ) : GenerationMviIntent - - data class SetAdvancedOptionsVisibility(val visible: Boolean) : GenerationMviIntent - - sealed interface Update : GenerationMviIntent { - - data class Prompt(val value: String) : Update - - data class NegativePrompt(val value: String) : Update - - sealed interface Size : Update { - - data class Width(val value: String) : Size - - data class Height(val value: String) : Size - } - - data class SamplingSteps(val value: Int) : Update - - data class CfgScale(val value: Float) : Update - - data class RestoreFaces(val value: Boolean) : Update - - data class Seed(val value: String) : Update - - data class SubSeed(val value: String) : Update - - data class SubSeedStrength(val value: Float) : Update - - data class Sampler(val value: String) : Update - - data class Nsfw(val value: Boolean) : Update - - data class Batch(val value: Int) : Update - - sealed interface OpenAi : Update { - - data class Model(val value: OpenAiModel) : OpenAi - - data class Size(val value: OpenAiSize) : OpenAi - - data class Quality(val value: OpenAiQuality) : OpenAi - - data class Style(val value: OpenAiStyle) : OpenAi - } - - sealed interface StabilityAi : Update { - data class Style(val value: StabilityAiStylePreset) : StabilityAi - - data class ClipGuidance(val value: StabilityAiClipGuidance) : StabilityAi - } - } - - sealed interface Result : GenerationMviIntent { - - data class Save(val ai: List) : Result - - data class View(val ai: AiGenerationResult) : Result - - data class Report(val ai: AiGenerationResult) : Result - } - - data class SetModal(val modal: Modal) : GenerationMviIntent - - enum class Cancel : GenerationMviIntent { - Generation, FetchRandomImage, - } - - data object Configuration : GenerationMviIntent - - data object Generate : GenerationMviIntent - - data class UpdateFromGeneration( - val payload: GenerationFormUpdateEvent.Payload, - ) : GenerationMviIntent - - data class Drawer(val intent: DrawerIntent) : GenerationMviIntent -} - -sealed interface ImageToImageIntent : GenerationMviIntent { - - data object InPaint : ImageToImageIntent - - data object FetchRandomPhoto : ImageToImageIntent - - data object ClearImageInput : ImageToImageIntent - - data class UpdateDenoisingStrength(val value: Float) : ImageToImageIntent - - data class UpdateImage(val bitmap: Bitmap) : ImageToImageIntent - - data class CropImage(val bitmap: Bitmap) : ImageToImageIntent - - enum class Pick : ImageToImageIntent { - Camera, Gallery - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviState.kt deleted file mode 100644 index 7e8f71304..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviState.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.shifthackz.aisdv1.presentation.core - -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.OpenAiQuality -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.OpenAiStyle -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.StabilityAiClipGuidance -import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.android.core.mvi.MviState - -abstract class GenerationMviState : MviState { - abstract val onBoardingDemo: Boolean - abstract val screenModal: Modal - abstract val mode: ServerSource - abstract val advancedToggleButtonVisible: Boolean - abstract val advancedOptionsVisible: Boolean - abstract val formPromptTaggedInput: Boolean - abstract val prompt: String - abstract val negativePrompt: String - abstract val width: String - abstract val height: String - abstract val samplingSteps: Int - abstract val cfgScale: Float - abstract val restoreFaces: Boolean - abstract val seed: String - abstract val subSeed: String - abstract val subSeedStrength: Float - abstract val selectedSampler: String - abstract val availableSamplers: List - abstract val selectedStylePreset: StabilityAiStylePreset - abstract val selectedClipGuidancePreset: StabilityAiClipGuidance - abstract val openAiModel: OpenAiModel - abstract val openAiSize: OpenAiSize - abstract val openAiQuality: OpenAiQuality - abstract val openAiStyle: OpenAiStyle - abstract val widthValidationError: UiText? - abstract val heightValidationError: UiText? - abstract val nsfw: Boolean - abstract val batchCount: Int - abstract val generateButtonEnabled: Boolean - - open val promptKeywords: List - get() = prompt.split(",") - .map { it.trim() } - .filter { it.isNotEmpty() } - - open val negativePromptKeywords: List - get() = negativePrompt.split(",") - .map { it.trim() } - .filter { it.isNotEmpty() } - - open val hasValidationErrors: Boolean - get() = widthValidationError != null || heightValidationError != null - - open fun copyState( - onBoardingDemo: Boolean = this.onBoardingDemo, - screenModal: Modal = this.screenModal, - mode: ServerSource = this.mode, - advancedToggleButtonVisible: Boolean = this.advancedToggleButtonVisible, - advancedOptionsVisible: Boolean = this.advancedOptionsVisible, - formPromptTaggedInput: Boolean = this.formPromptTaggedInput, - prompt: String = this.prompt, - negativePrompt: String = this.negativePrompt, - width: String = this.width, - height: String = this.height, - samplingSteps: Int = this.samplingSteps, - cfgScale: Float = this.cfgScale, - restoreFaces: Boolean = this.restoreFaces, - seed: String = this.seed, - subSeed: String = this.subSeed, - subSeedStrength: Float = this.subSeedStrength, - selectedSampler: String = this.selectedSampler, - availableSamplers: List = this.availableSamplers, - selectedStylePreset: StabilityAiStylePreset = this.selectedStylePreset, - selectedClipGuidancePreset: StabilityAiClipGuidance = this.selectedClipGuidancePreset, - openAiModel: OpenAiModel = this.openAiModel, - openAiSize: OpenAiSize = this.openAiSize, - openAiQuality: OpenAiQuality = this.openAiQuality, - openAiStyle: OpenAiStyle = this.openAiStyle, - widthValidationError: UiText? = this.widthValidationError, - heightValidationError: UiText? = this.heightValidationError, - nsfw: Boolean = this.nsfw, - batchCount: Int = this.batchCount, - generateButtonEnabled: Boolean = this.generateButtonEnabled, - ): GenerationMviState = this -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviViewModel.kt deleted file mode 100644 index 187aa780e..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviViewModel.kt +++ /dev/null @@ -1,335 +0,0 @@ -@file:Suppress("UNCHECKED_CAST") - -package com.shifthackz.aisdv1.presentation.core - -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.StabilityAiSampler -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.caching.SaveLastResultToCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.SaveGenerationResultUseCase -import com.shifthackz.aisdv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.screen.txt2img.mapToUi -import com.shifthackz.android.core.mvi.MviEffect -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.kotlin.subscribeBy -import java.util.concurrent.TimeUnit - -abstract class GenerationMviViewModel( - private val preferenceManager: PreferenceManager, - getStableDiffusionSamplersUseCase: GetStableDiffusionSamplersUseCase, - observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, - private val saveLastResultToCacheUseCase: SaveLastResultToCacheUseCase, - private val saveGenerationResultUseCase: SaveGenerationResultUseCase, - private val interruptGenerationUseCase: InterruptGenerationUseCase, - private val mainRouter: MainRouter, - private val drawerRouter: DrawerRouter, - private val dimensionValidator: DimensionValidator, - private val schedulersProvider: SchedulersProvider, - private val backgroundWorkObserver: BackgroundWorkObserver, -) : MviRxViewModel() { - - private var generationDisposable: Disposable? = null - private var randomImageDisposable: Disposable? = null - - init { - !preferenceManager - .observe() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onComplete = EmptyLambda, - onNext = { settings -> - updateGenerationState { - it - .copyState( - mode = settings.source, - advancedToggleButtonVisible = !settings.formAdvancedOptionsAlwaysShow, - formPromptTaggedInput = settings.formPromptTaggedInput, - ) - .let { state -> - if (!settings.formAdvancedOptionsAlwaysShow) state - else state.copyState(advancedOptionsVisible = true) - } - } - } - ) - - !getStableDiffusionSamplersUseCase() - .delay(500L, TimeUnit.MILLISECONDS) - .map { samplers -> samplers.map(StableDiffusionSampler::name) } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onSuccess = { samplers -> - updateGenerationState { state -> - val allSamplers = when (state.mode) { - ServerSource.STABILITY_AI -> StabilityAiSampler.entries.map { "$it" } - else -> samplers - } - state.copyState( - availableSamplers = allSamplers, - selectedSampler = allSamplers.firstOrNull() ?: "", - ) - } - } - ) - - !observeHordeProcessStatusUseCase() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onNext = ::onReceivedHordeStatus, - onComplete = EmptyLambda, - ) - - !observeLocalDiffusionProcessStatusUseCase() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onNext = ::onReceivedLocalDiffusionStatus, - onComplete = EmptyLambda, - ) - } - - abstract fun generateDisposable(): Disposable - - abstract fun generateBackground() - - open fun onReceivedHordeStatus(status: HordeProcessStatus) {} - - open fun onReceivedLocalDiffusionStatus(status: LocalDiffusionStatus) {} - - override fun onCleared() { - super.onCleared() - generationDisposable?.dispose() - generationDisposable = null - randomImageDisposable?.dispose() - randomImageDisposable = null - } - - override fun processIntent(intent: I) { - when (intent) { - is GenerationMviIntent.NewPrompts -> updateGenerationState { - it.copyState( - prompt = intent.positive.trim(), - negativePrompt = intent.negative.trim(), - ) - } - - is GenerationMviIntent.SetAdvancedOptionsVisibility -> updateGenerationState { - it.copyState(advancedOptionsVisible = intent.visible) - } - - is GenerationMviIntent.Update.Prompt -> updateGenerationState { - it.copyState(prompt = intent.value) - } - - is GenerationMviIntent.Update.NegativePrompt -> updateGenerationState { - it.copyState(negativePrompt = intent.value) - } - - is GenerationMviIntent.Update.Size.Width -> updateGenerationState { - it.copyState( - width = intent.value, - widthValidationError = dimensionValidator(intent.value).mapToUi(), - ) - } - - is GenerationMviIntent.Update.Size.Height -> updateGenerationState { - it.copyState( - height = intent.value, - heightValidationError = dimensionValidator(intent.value).mapToUi(), - ) - } - - is GenerationMviIntent.Update.SamplingSteps -> updateGenerationState { - it.copyState(samplingSteps = intent.value) - } - - is GenerationMviIntent.Update.CfgScale -> updateGenerationState { - it.copyState(cfgScale = intent.value) - } - - is GenerationMviIntent.Update.RestoreFaces -> updateGenerationState { - it.copyState(restoreFaces = intent.value) - } - - is GenerationMviIntent.Update.Seed -> updateGenerationState { - it.copyState(seed = intent.value) - } - - is GenerationMviIntent.Update.SubSeed -> updateGenerationState { - it.copyState(subSeed = intent.value) - } - - is GenerationMviIntent.Update.SubSeedStrength -> updateGenerationState { - it.copyState(subSeedStrength = intent.value) - } - - is GenerationMviIntent.Update.Sampler -> updateGenerationState { - it.copyState(selectedSampler = intent.value) - } - - is GenerationMviIntent.Update.Nsfw -> updateGenerationState { - it.copyState(nsfw = intent.value) - } - - is GenerationMviIntent.Update.Batch -> updateGenerationState { - it.copyState(batchCount = intent.value) - } - - is GenerationMviIntent.Update.OpenAi.Model -> updateGenerationState { state -> - val size = if (state.openAiSize.supportedModels.contains(intent.value)) { - state.openAiSize - } else { - OpenAiSize.entries.first { it.supportedModels.contains(intent.value) } - } - state.copyState(openAiModel = intent.value, openAiSize = size) - } - - is GenerationMviIntent.Update.OpenAi.Size -> updateGenerationState { - it.copyState(openAiSize = intent.value) - } - - is GenerationMviIntent.Update.OpenAi.Quality -> updateGenerationState { - it.copyState(openAiQuality = intent.value) - } - - is GenerationMviIntent.Update.OpenAi.Style -> updateGenerationState { - it.copyState(openAiStyle = intent.value) - } - - is GenerationMviIntent.Result.Save -> !Observable - .fromIterable(intent.ai) - .flatMapCompletable(saveGenerationResultUseCase::invoke) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { setActiveModal(Modal.None) } - - is GenerationMviIntent.Result.View -> !saveLastResultToCacheUseCase(intent.ai) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { mainRouter.navigateToGalleryDetails(it.id) } - - is GenerationMviIntent.Result.Report -> mainRouter.navigateToReportImage(intent.ai.id) - - is GenerationMviIntent.SetModal -> setActiveModal(intent.modal) - - GenerationMviIntent.Cancel.Generation -> { - generationDisposable?.dispose() - generationDisposable = null - !interruptGenerationUseCase() - .doOnSubscribe { setActiveModal(Modal.None) } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) - } - - GenerationMviIntent.Cancel.FetchRandomImage -> { - randomImageDisposable?.dispose() - randomImageDisposable = null - setActiveModal(Modal.None) - } - - GenerationMviIntent.Generate -> { - if (backgroundWorkObserver.hasActiveTasks()) { - setActiveModal(Modal.Background.Running) - } else { - if (preferenceManager.backgroundGeneration) { - generateBackground() - backgroundWorkObserver.refreshStatus() - setActiveModal(Modal.Background.Scheduled) - } else { - generateOnUi { generateDisposable() } - } - } - } - - GenerationMviIntent.Configuration -> mainRouter.navigateToServerSetup( - LaunchSource.SETTINGS, - ) - - is GenerationMviIntent.UpdateFromGeneration -> { - updateFormPreviousAiGeneration(intent.payload) - } - - is GenerationMviIntent.Drawer -> when (intent.intent) { - DrawerIntent.Close -> drawerRouter.closeDrawer() - DrawerIntent.Open -> drawerRouter.openDrawer() - } - - else -> Unit - } - } - - protected open fun updateFormPreviousAiGeneration(payload: GenerationFormUpdateEvent.Payload) { - val ai = when (payload) { - is GenerationFormUpdateEvent.Payload.I2IForm -> payload.ai - is GenerationFormUpdateEvent.Payload.T2IForm -> payload.ai - else -> return - } - updateGenerationState { oldState -> - oldState - .copyState( - advancedOptionsVisible = true, - prompt = ai.prompt, - negativePrompt = ai.negativePrompt, - width = "${ai.width}", - height = "${ai.height}", - seed = ai.seed, - subSeed = ai.subSeed, - subSeedStrength = ai.subSeedStrength, - samplingSteps = ai.samplingSteps, - cfgScale = ai.cfgScale, - restoreFaces = ai.restoreFaces, - ) - .let { state -> - if (!state.availableSamplers.contains(ai.sampler)) state - else state.copyState(selectedSampler = ai.sampler) - } - } - } - - protected fun setActiveModal(modal: Modal) = updateGenerationState { - it.copyState(screenModal = modal) - } - - protected fun fetchRandomImage(fn: () -> Disposable) { - randomImageDisposable?.dispose() - randomImageDisposable = null - val newDisposable = fn() - randomImageDisposable = newDisposable - randomImageDisposable?.addToDisposable() - } - - private fun generateOnUi(fn: () -> Disposable) { - generationDisposable?.dispose() - generationDisposable = null - val newDisposable = fn() - generationDisposable = newDisposable - generationDisposable?.addToDisposable() - } - - private fun updateGenerationState(mutation: (GenerationMviState) -> GenerationMviState) = - runCatching { - updateState(mutation as (S) -> S) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/NavigationModule.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/NavigationModule.kt deleted file mode 100644 index 21110ee4d..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/NavigationModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.presentation.di - -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouterImpl -import com.shifthackz.aisdv1.presentation.navigation.router.home.HomeRouter -import com.shifthackz.aisdv1.presentation.navigation.router.home.HomeRouterImpl -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouterImpl -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind -import org.koin.dsl.module - -internal val navigationModule = module { - singleOf(::MainRouterImpl) bind MainRouter::class - singleOf(::DrawerRouterImpl) bind DrawerRouter::class - singleOf(::HomeRouterImpl) bind HomeRouter::class -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/PresentationModule.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/PresentationModule.kt deleted file mode 100644 index e124fa1cd..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/PresentationModule.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.shifthackz.aisdv1.presentation.di - -val presentationModule = (navigationModule + viewModelModule + uiUtilsModule).toTypedArray() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/UiUtilsModule.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/UiUtilsModule.kt deleted file mode 100644 index 287d94c00..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/UiUtilsModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.presentation.di - -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor -import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailBitmapExporter -import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailSharing -import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryExporter -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintStateProducer -import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal val uiUtilsModule = module { - factoryOf(::GalleryExporter) - factoryOf(::GalleryDetailBitmapExporter) - factoryOf(::GalleryDetailSharing) - singleOf(::GenerationFormUpdateEvent) - singleOf(::DebugMenuAccessor) - singleOf(::InPaintStateProducer) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt deleted file mode 100755 index 054bdb337..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt +++ /dev/null @@ -1,151 +0,0 @@ -package com.shifthackz.aisdv1.presentation.di - -import com.shifthackz.aisdv1.presentation.activity.AiStableDiffusionViewModel -import com.shifthackz.aisdv1.presentation.modal.download.DownloadDialogViewModel -import com.shifthackz.aisdv1.presentation.modal.embedding.EmbeddingViewModel -import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasViewModel -import com.shifthackz.aisdv1.presentation.modal.history.InputHistoryViewModel -import com.shifthackz.aisdv1.presentation.modal.tag.EditTagViewModel -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuViewModel -import com.shifthackz.aisdv1.presentation.screen.donate.DonateViewModel -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerViewModel -import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailViewModel -import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryViewModel -import com.shifthackz.aisdv1.presentation.screen.home.HomeNavigationViewModel -import com.shifthackz.aisdv1.presentation.screen.img2img.ImageToImageViewModel -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintViewModel -import com.shifthackz.aisdv1.presentation.screen.loader.ConfigurationLoaderViewModel -import com.shifthackz.aisdv1.presentation.screen.logger.LoggerViewModel -import com.shifthackz.aisdv1.presentation.screen.onboarding.OnBoardingViewModel -import com.shifthackz.aisdv1.presentation.screen.report.ReportViewModel -import com.shifthackz.aisdv1.presentation.screen.settings.SettingsViewModel -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupViewModel -import com.shifthackz.aisdv1.presentation.screen.splash.SplashViewModel -import com.shifthackz.aisdv1.presentation.screen.txt2img.TextToImageViewModel -import com.shifthackz.aisdv1.presentation.screen.web.webui.WebUiViewModel -import com.shifthackz.aisdv1.presentation.theme.global.AiSdAppThemeViewModel -import com.shifthackz.aisdv1.presentation.widget.connectivity.ConnectivityViewModel -import com.shifthackz.aisdv1.presentation.widget.engine.EngineSelectionViewModel -import com.shifthackz.aisdv1.presentation.widget.work.BackgroundWorkViewModel -import org.koin.core.module.dsl.viewModel -import org.koin.core.module.dsl.viewModelOf -import org.koin.dsl.module - -val viewModelModule = module { - viewModelOf(::AiStableDiffusionViewModel) - viewModelOf(::AiSdAppThemeViewModel) - viewModelOf(::SplashViewModel) - viewModelOf(::DrawerViewModel) - viewModelOf(::HomeNavigationViewModel) - viewModelOf(::ConfigurationLoaderViewModel) - viewModelOf(::TextToImageViewModel) - viewModelOf(::SettingsViewModel) - viewModelOf(::GalleryViewModel) - viewModelOf(::ConnectivityViewModel) - viewModelOf(::InputHistoryViewModel) - viewModelOf(::DebugMenuViewModel) - viewModelOf(::ExtrasViewModel) - viewModelOf(::EmbeddingViewModel) - viewModelOf(::EditTagViewModel) - viewModelOf(::InPaintViewModel) - viewModelOf(::EngineSelectionViewModel) - viewModelOf(::WebUiViewModel) - viewModelOf(::DonateViewModel) - viewModelOf(::BackgroundWorkViewModel) - viewModelOf(::LoggerViewModel) - viewModelOf(::DownloadDialogViewModel) - - viewModel { parameters -> - OnBoardingViewModel( - launchSource = LaunchSource.fromKey(parameters.get()), - dispatchersProvider = get(), - mainRouter = get(), - splashNavigationUseCase = get(), - preferenceManager = get(), - schedulersProvider = get(), - buildInfoProvider = get(), - ) - } - - viewModel { parameters -> - val launchSource = LaunchSource.fromKey(parameters.get()) - ServerSetupViewModel( - launchSource = launchSource, - dispatchersProvider = get(), - getConfigurationUseCase = get(), - getLocalOnnxModelsUseCase = get(), - getLocalMediaPipeModelsUseCase = get(), - fetchAndGetHuggingFaceModelsUseCase = get(), - urlValidator = get(), - stringValidator = get(), - filePathValidator = get(), - setupConnectionInterActor = get(), - downloadModelUseCase = get(), - deleteModelUseCase = get(), - schedulersProvider = get(), - preferenceManager = get(), - wakeLockInterActor = get(), - mainRouter = get(), - buildInfoProvider = get(), - ) - } - - viewModel { parameters -> - GalleryDetailViewModel( - itemId = parameters.get(), - dispatchersProvider = get(), - buildInfoProvider = get(), - getGenerationResultUseCase = get(), - getLastResultFromCacheUseCase = get(), - deleteGalleryItemUseCase = get(), - toggleImageVisibilityUseCase = get(), - galleryDetailBitmapExporter = get(), - base64ToBitmapConverter = get(), - schedulersProvider = get(), - generationFormUpdateEvent = get(), - mainRouter = get(), - ) - } - - viewModel { parameters -> - ReportViewModel( - itemId = parameters.get(), - sendReportUseCase = get(), - getGenerationResultUseCase = get(), - getLastResultFromCacheUseCase = get(), - base64ToBitmapConverter = get(), - mainRouter = get(), - schedulersProvider = get(), - buildInfoProvider = get(), - ) - } - - viewModel { - ImageToImageViewModel( - dispatchersProvider = get(), - generationFormUpdateEvent = get(), - getStableDiffusionSamplersUseCase = get(), - observeHordeProcessStatusUseCase = get(), - observeLocalDiffusionProcessStatusUseCase = get(), - saveLastResultToCacheUseCase = get(), - saveGenerationResultUseCase = get(), - interruptGenerationUseCase = get(), - drawerRouter = get(), - dimensionValidator = get(), - imageToImageUseCase = get(), - getRandomImageUseCase = get(), - bitmapToBase64Converter = get(), - base64ToBitmapConverter = get(), - preferenceManager = get(), - schedulersProvider = get(), - notificationManager = get(), - wakeLockInterActor = get(), - inPaintStateProducer = get(), - mainRouter = get(), - backgroundTaskManager = get(), - backgroundWorkObserver = get(), - buildInfoProvider = get(), - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/extensions/BooleanExtensions.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/extensions/BooleanExtensions.kt deleted file mode 100644 index 6d215d1fe..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/extensions/BooleanExtensions.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.presentation.extensions - -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -fun Boolean.mapToUi(): UiText = (if (this) LocalizationR.string.yes else LocalizationR.string.no).asUiText() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/extensions/NavControllerExtensions.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/extensions/NavControllerExtensions.kt deleted file mode 100644 index 96ffcf0f9..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/extensions/NavControllerExtensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.shifthackz.aisdv1.presentation.extensions - -import androidx.navigation.NavController -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute - -fun NavController.navigatePopUpToCurrent(navRoute: NavigationRoute) { - navigate(navRoute) { - currentBackStackEntry?.destination?.route?.let { - popUpTo(it) { inclusive = true } - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/crop/CropImageModal.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/crop/CropImageModal.kt deleted file mode 100644 index f16ac561c..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/crop/CropImageModal.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.shifthackz.aisdv1.presentation.modal.crop - -import android.graphics.Bitmap -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.graphics.asAndroidBitmap -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.platform.LocalContext -import com.mr0xf00.easycrop.AspectRatio -import com.mr0xf00.easycrop.CropError -import com.mr0xf00.easycrop.CropResult -import com.mr0xf00.easycrop.CropperStyle -import com.mr0xf00.easycrop.CropperStyleGuidelines -import com.mr0xf00.easycrop.RectCropShape -import com.mr0xf00.easycrop.crop -import com.mr0xf00.easycrop.rememberImageCropper -import com.mr0xf00.easycrop.ui.ImageCropperDialog -import com.shifthackz.aisdv1.core.common.extensions.showToast - -@Composable -fun CropImageModal( - bitmap: Bitmap, - onResult: (Bitmap) -> Unit = {}, - onDismissRequest: () -> Unit = {}, -) { - val imageCropper = rememberImageCropper() - val state = imageCropper.cropState - state?.let { - LaunchedEffect(Unit) { - it.region = when { - it.region.height > it.region.width -> it.region.copy( - bottom = it.region.width, - ) - - it.region.width > it.region.height -> it.region.copy( - right = it.region.height - ) - - else -> it.region - } - it.aspectLock = true - } - ImageCropperDialog( - state = it, - style = CropperStyle( - backgroundColor = MaterialTheme.colorScheme.background, - overlay = MaterialTheme.colorScheme.surface, - guidelines = CropperStyleGuidelines(), - shapes = listOf(RectCropShape), - aspects = listOf(AspectRatio(1, 1)), - ), - ) - } - val context = LocalContext.current - LaunchedEffect(Unit) { - when (val result = imageCropper.crop(bmp = bitmap.asImageBitmap())) { - is CropResult.Success -> result.bitmap.asAndroidBitmap().let(onResult::invoke) - - CropError.LoadingError -> { - context.showToast("Loading error") - onDismissRequest() - } - - CropError.SavingError -> { - context.showToast("Saving error") - onDismissRequest() - } - - CropResult.Cancelled -> onDismissRequest() - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogViewModel.kt deleted file mode 100644 index 2d648714c..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogViewModel.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.shifthackz.aisdv1.presentation.modal.download - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalModelUseCase -import io.reactivex.rxjava3.kotlin.subscribeBy - -class DownloadDialogViewModel( - private val getLocalModelUseCase: GetLocalModelUseCase, - private val schedulersProvider: SchedulersProvider, - dispatchersProvider: DispatchersProvider, -) : MviRxViewModel() { - - override val initialState = DownloadDialogState() - - override val effectDispatcher = dispatchersProvider.immediate - - override fun processIntent(intent: DownloadDialogIntent) { - when (intent) { - is DownloadDialogIntent.LoadModelData -> !getLocalModelUseCase(intent.id) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { model -> - updateState { - it.copy(sources = model.sources.mapIndexed { i, url -> url to (i == 0) }) - } - } - - is DownloadDialogIntent.SelectSource -> updateState { - it.copy(sources = it.sources.map { (url, _) -> url to (url == intent.url) }) - } - - DownloadDialogIntent.StartDownload -> emitEffect( - DownloadDialogEffect.StartDownload(currentState.selectedUrl) - ) - - DownloadDialogIntent.Close -> emitEffect(DownloadDialogEffect.Close) - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasState.kt deleted file mode 100644 index 5860bfe04..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasState.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.shifthackz.aisdv1.presentation.modal.extras - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.android.core.mvi.MviState - -@Immutable -data class ExtrasState( - val loading: Boolean = true, - val source: ServerSource = ServerSource.AUTOMATIC1111, - val error: ErrorState = ErrorState.None, - val prompt: String = "", - val negativePrompt: String = "", - val type: ExtraType = ExtraType.Lora, - val loras: List = emptyList(), -) : MviState - -@Immutable -data class ExtraItemUi( - val type: ExtraType, - val key: String, - val name: String, - val alias: String?, - val isApplied: Boolean, - val value: String? = null, -) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryItemUi.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryItemUi.kt deleted file mode 100644 index 33eec308a..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryItemUi.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.modal.history - -import android.graphics.Bitmap -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult - -@Immutable -data class InputHistoryItemUi( - val generationResult: AiGenerationResult, - val bitmap: Bitmap, -) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryViewModel.kt deleted file mode 100644 index c8290722c..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryViewModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.shifthackz.aisdv1.presentation.modal.history - -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase -import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryPagingSource -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.android.core.mvi.EmptyEffect -import com.shifthackz.android.core.mvi.EmptyIntent -import com.shifthackz.android.core.mvi.EmptyState -import kotlinx.coroutines.flow.Flow - -class InputHistoryViewModel( - dispatchersProvider: DispatchersProvider, - private val getGenerationResultPagedUseCase: GetGenerationResultPagedUseCase, - private val base64ToBitmapConverter: Base64ToBitmapConverter, - private val schedulersProvider: SchedulersProvider, -) : MviRxViewModel() { - - override val initialState = EmptyState - - override val effectDispatcher = dispatchersProvider.immediate - - private val config = PagingConfig( - pageSize = Constants.PAGINATION_PAYLOAD_SIZE, - initialLoadSize = Constants.PAGINATION_PAYLOAD_SIZE - ) - - private val pager: Pager = Pager( - config = config, - initialKey = GalleryPagingSource.FIRST_KEY, - pagingSourceFactory = { - InputHistoryPagingSource( - getGenerationResultPagedUseCase, - base64ToBitmapConverter, - schedulersProvider, - ) - } - ) - - val pagingFlow: Flow> = pager.flow -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/ErrorState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/ErrorState.kt deleted file mode 100644 index edc8b381f..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/ErrorState.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.presentation.model - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.android.core.mvi.MviState - -sealed interface ErrorState : MviState { - - data object None : ErrorState - - data object Generic : ErrorState - - @Immutable - data class WithMessage(val message: UiText) : ErrorState -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/ExtraType.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/ExtraType.kt deleted file mode 100644 index fcf770894..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/ExtraType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.presentation.model - -enum class ExtraType(val raw: String) { - Lora("lora"), - HyperNet("hyper_net"); -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/MotionEvent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/MotionEvent.kt deleted file mode 100644 index 79dd12cd8..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/MotionEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.presentation.model - -enum class MotionEvent { - Idle, Down, Move, Up -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt deleted file mode 100644 index a5b0463da..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.graph - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DeveloperMode -import androidx.compose.material.icons.filled.SettingsEthernet -import androidx.compose.material.icons.filled.Web -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.FeatureTag -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.model.NavItem -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.aisdv1.presentation.widget.source.getNameUiText -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -fun mainDrawerNavItems(settings: Settings? = null): List = buildList { - add(txt2ImgTab().copy(name = LocalizationR.string.title_text_to_image.asUiText())) - add(img2imgTab().copy(name = LocalizationR.string.title_image_to_image.asUiText())) - add(galleryTab()) - settings?.source?.takeIf { it.featureTags.contains(FeatureTag.OwnServer) }?.let { - add(webUi(it)) - } - add(settingsTab()) - add(configuration()) - settings?.developerMode?.takeIf { it }?.let { - add(developerMode()) - } -} - -private fun webUi(source: ServerSource) = NavItem( - name = UiText.Concat( - LocalizationR.string.drawer_web_ui.asUiText(), - " (".asUiText(), - source.getNameUiText(), - ")".asUiText(), - ), - navRoute = NavigationRoute.WebUi, - icon = NavItem.Icon.Vector( - vector = Icons.Default.Web, - ), -) - -private fun configuration() = NavItem( - name = LocalizationR.string.settings_item_config.asUiText(), - navRoute = NavigationRoute.ServerSetup(source = LaunchSource.SETTINGS), - icon = NavItem.Icon.Vector( - vector = Icons.Default.SettingsEthernet, - ), -) - -private fun developerMode() = NavItem( - name = LocalizationR.string.title_debug_menu.asUiText(), - navRoute = NavigationRoute.Debug, - icon = NavItem.Icon.Vector( - vector = Icons.Default.DeveloperMode, - ) -) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/HomeNavGraph.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/HomeNavGraph.kt deleted file mode 100644 index dfccf1b30..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/HomeNavGraph.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.graph - -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Settings -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.model.NavItem -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.aisdv1.presentation.navigation.router.home.HomeRouter -import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryScreen -import com.shifthackz.aisdv1.presentation.screen.home.HomeNavigationScreen -import com.shifthackz.aisdv1.presentation.screen.img2img.ImageToImageScreen -import com.shifthackz.aisdv1.presentation.screen.settings.SettingsScreen -import com.shifthackz.aisdv1.presentation.screen.txt2img.TextToImageScreen -import org.koin.compose.koinInject -import com.shifthackz.aisdv1.core.localization.R as LocalizationR -import com.shifthackz.aisdv1.presentation.R as PresentationR - -fun NavGraphBuilder.homeScreenNavGraph() { - composable { - HomeNavigationScreen( - navItems = listOf( - txt2ImgTab(), - img2imgTab(), - galleryTab(), - settingsTab(), - ), - ) - } -} - -fun txt2ImgTab() = NavItem( - name = LocalizationR.string.home_tab_txt_to_img.asUiText(), - navRoute = NavigationRoute.HomeNavigation.TxtToImg, - icon = NavItem.Icon.Resource( - resId = PresentationR.drawable.ic_text, - modifier = Modifier.size(24.dp), - ), - content = { - HomeTabBase(NavigationRoute.HomeNavigation.TxtToImg) { - TextToImageScreen() - } - }, -) - -fun img2imgTab() = NavItem( - name = LocalizationR.string.home_tab_img_to_img.asUiText(), - navRoute = NavigationRoute.HomeNavigation.ImgToImg, - icon = NavItem.Icon.Resource( - resId = PresentationR.drawable.ic_image, - modifier = Modifier.size(24.dp), - ), - content = { - HomeTabBase(NavigationRoute.HomeNavigation.ImgToImg) { - ImageToImageScreen() - } - }, -) - -fun galleryTab() = NavItem( - name = LocalizationR.string.home_tab_gallery.asUiText(), - navRoute = NavigationRoute.HomeNavigation.Gallery, - icon = NavItem.Icon.Resource( - resId = PresentationR.drawable.ic_gallery, - modifier = Modifier.size(24.dp), - ), - content = { - HomeTabBase(NavigationRoute.HomeNavigation.Gallery) { - GalleryScreen() - } - }, -) - -fun settingsTab() = NavItem( - name = LocalizationR.string.home_tab_settings.asUiText(), - navRoute = NavigationRoute.HomeNavigation.Settings, - icon = NavItem.Icon.Vector( - vector = Icons.Default.Settings, - ), - content = { - HomeTabBase(NavigationRoute.HomeNavigation.Settings) { - SettingsScreen() - } - } -) - -@Composable -private fun HomeTabBase( - navRoute: NavigationRoute, - content: @Composable () -> Unit, -) { - val homeRouter: HomeRouter = koinInject() - LaunchedEffect(Unit) { - homeRouter.updateExternallyWithoutNavigation(navRoute = navRoute) - } - content() -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt deleted file mode 100644 index e9de82a67..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.graph - -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType -import androidx.navigation.compose.composable -import androidx.navigation.toRoute -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuScreen -import com.shifthackz.aisdv1.presentation.screen.donate.DonateScreen -import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailScreen -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintScreen -import com.shifthackz.aisdv1.presentation.screen.loader.ConfigurationLoaderScreen -import com.shifthackz.aisdv1.presentation.screen.logger.LoggerScreen -import com.shifthackz.aisdv1.presentation.screen.onboarding.OnBoardingScreen -import com.shifthackz.aisdv1.presentation.screen.onboarding.OnBoardingViewModel -import com.shifthackz.aisdv1.presentation.screen.report.ReportScreen -import com.shifthackz.aisdv1.presentation.screen.report.ReportViewModel -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupScreen -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupViewModel -import com.shifthackz.aisdv1.presentation.screen.splash.SplashScreen -import com.shifthackz.aisdv1.presentation.screen.web.webui.WebUiScreen -import org.koin.androidx.compose.koinViewModel -import org.koin.compose.koinInject -import org.koin.core.parameter.parametersOf -import kotlin.reflect.typeOf - -fun NavGraphBuilder.mainNavGraph() { - - composable { - SplashScreen() - } - - composable( - typeMap = mapOf( - typeOf() to NavType.EnumType(LaunchSource::class.java) - ) - ) { entry -> - val sourceKey = entry.toRoute().source.ordinal - ServerSetupScreen( - viewModel = koinViewModel( - parameters = { parametersOf(sourceKey) } - ), - buildInfoProvider = koinInject() - ) - } - - composable { - ConfigurationLoaderScreen() - } - - homeScreenNavGraph() - - composable { entry -> - val itemId = entry.toRoute().itemId - GalleryDetailScreen(itemId = itemId) - } - - composable { entry -> - val itemId = entry.toRoute().itemId - ReportScreen( - viewModel = koinViewModel( - parameters = { parametersOf(itemId) } - ), - ) - } - - composable { - DebugMenuScreen() - } - - composable { - LoggerScreen() - } - - composable { - InPaintScreen() - } - - composable { - WebUiScreen() - } - - composable { - DonateScreen() - } - - composable( - typeMap = mapOf( - typeOf() to NavType.EnumType(LaunchSource::class.java) - ) - ) { entry -> - val sourceKey = entry.toRoute().source.ordinal - OnBoardingScreen( - viewModel = koinViewModel( - parameters = { parametersOf(sourceKey) } - ), - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/Router.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/Router.kt deleted file mode 100644 index b5b336637..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/Router.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.router - -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import io.reactivex.rxjava3.core.Observable - -interface Router { - fun observe(): Observable -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouter.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouter.kt deleted file mode 100644 index 8eab1ef3c..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.drawer - -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.router.Router - -interface DrawerRouter : Router { - - fun openDrawer() - - fun closeDrawer() -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/home/HomeRouter.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/home/HomeRouter.kt deleted file mode 100644 index daaafd6f4..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/home/HomeRouter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.home - -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.aisdv1.presentation.navigation.router.Router - -interface HomeRouter : Router { - - fun updateExternallyWithoutNavigation(navRoute: NavigationRoute) - - fun navigateToRoute(navRoute: NavigationRoute) - - fun navigateToTxt2Img() - - fun navigateToImg2Img() - - fun navigateToGallery() - - fun navigateToSettings() -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt deleted file mode 100644 index ed63759da..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.main - -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.router.Router - -interface MainRouter : Router { - - fun navigateBack() - - fun navigateToOnBoarding(source: LaunchSource) - - fun navigateToPostSplashConfigLoader() - - fun navigateToHomeScreen() - - fun navigateToServerSetup(source: LaunchSource) - - fun navigateToGalleryDetails(itemId: Long) - - fun navigateToReportImage(itemId: Long) - - fun navigateToInPaint() - - fun navigateToDonate() - - fun navigateToDebugMenu() - - fun navigateToLogger() -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterExtensions.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterExtensions.kt deleted file mode 100644 index ed0df2b95..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterExtensions.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.main - -import com.shifthackz.aisdv1.domain.usecase.splash.SplashNavigationUseCase -import com.shifthackz.aisdv1.presentation.model.LaunchSource - -fun MainRouter.postSplashNavigation( - action: SplashNavigationUseCase.Action, -) { - when (action) { - SplashNavigationUseCase.Action.LAUNCH_ONBOARDING -> navigateToOnBoarding( - source = LaunchSource.SPLASH, - ) - SplashNavigationUseCase.Action.LAUNCH_SERVER_SETUP -> navigateToServerSetup( - source = LaunchSource.SPLASH, - ) - SplashNavigationUseCase.Action.LAUNCH_HOME -> navigateToPostSplashConfigLoader() - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt deleted file mode 100644 index b080c44b0..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.debug - -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.presentation.utils.Constants - -class DebugMenuAccessor( - private val preferenceManager: PreferenceManager, -) { - - private var tapCount = 0; - - operator fun invoke(): Boolean { - if (preferenceManager.developerMode) { - return true - } - tapCount++ - if (tapCount >= Constants.DEBUG_MENU_ACCESS_TAPS) { - tapCount = 0 - preferenceManager.developerMode = true - return true - } - return false - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt deleted file mode 100644 index 132c65229..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.debug - -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.android.core.mvi.MviEffect - -sealed interface DebugMenuEffect : MviEffect { - data class Message(val message: UiText) : DebugMenuEffect -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt deleted file mode 100644 index 3d2c705f9..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.debug - -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -fun SchedulersToken.mapToUi(): UiText = when (this) { - SchedulersToken.MAIN_THREAD -> LocalizationR.string.scheduler_main - SchedulersToken.IO_THREAD -> LocalizationR.string.scheduler_io - SchedulersToken.COMPUTATION -> LocalizationR.string.scheduler_computation - SchedulersToken.SINGLE_THREAD -> LocalizationR.string.scheduler_single_thread -}.asUiText() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt deleted file mode 100644 index 20fa36e49..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.debug - -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.android.core.mvi.MviState - -data class DebugMenuState( - val screenModal: Modal = Modal.None, - val localDiffusionAllowCancel: Boolean = false, - val localDiffusionSchedulerThread: SchedulersToken = SchedulersToken.COMPUTATION, -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateEffect.kt deleted file mode 100644 index 98d548167..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateEffect.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.donate - -import com.shifthackz.android.core.mvi.MviEffect - -sealed interface DonateEffect : MviEffect { - data class OpenUrl(val url: String) : DonateEffect -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateState.kt deleted file mode 100644 index 5bfc02d12..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.donate - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.Supporter -import com.shifthackz.android.core.mvi.MviState - -@Immutable -data class DonateState( - val loading: Boolean = true, - val supporters: List = emptyList(), -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModel.kt deleted file mode 100644 index dafb978af..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.donate - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.usecase.donate.FetchAndGetSupportersUseCase -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import io.reactivex.rxjava3.kotlin.subscribeBy - -class DonateViewModel( - dispatchersProvider: DispatchersProvider, - schedulersProvider: SchedulersProvider, - fetchAndGetSupportersUseCase: FetchAndGetSupportersUseCase, - private val mainRouter: MainRouter, -) : MviRxViewModel() { - - override val initialState = DonateState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !fetchAndGetSupportersUseCase() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = { t -> - updateState { it.copy(loading = false) } - errorLog(t) - }, - onSuccess = { supporters -> - updateState { - it.copy( - loading = false, - supporters = supporters, - ) - } - }, - ) - } - - override fun processIntent(intent: DonateIntent) { - when (intent) { - is DonateIntent.LaunchUrl -> emitEffect(DonateEffect.OpenUrl(intent.url)) - DonateIntent.NavigateBack -> mainRouter.navigateBack() - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailBitmapExporter.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailBitmapExporter.kt deleted file mode 100644 index c322b6544..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailBitmapExporter.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.detail - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.presentation.utils.FileSavableExporter -import io.reactivex.rxjava3.core.Single -import java.io.File - -class GalleryDetailBitmapExporter( - override val fileProviderDescriptor: FileProviderDescriptor, -) : FileSavableExporter.BmpToFile { - - operator fun invoke(bitmap: Bitmap): Single = saveBitmapToFile( - System.currentTimeMillis().toString(), - bitmap - ) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailEffect.kt deleted file mode 100644 index 3668910f7..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailEffect.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.detail - -import com.shifthackz.android.core.mvi.MviEffect -import java.io.File - -sealed interface GalleryDetailEffect : MviEffect { - - data class ShareImageFile(val file: File) : GalleryDetailEffect - - data class ShareGenerationParams(val state: GalleryDetailState) : GalleryDetailEffect - - data class ShareClipBoard(val text: String) : GalleryDetailEffect -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt deleted file mode 100644 index a7e03168f..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.detail - -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface GalleryDetailIntent : MviIntent { - - data object NavigateBack : GalleryDetailIntent - - data class SelectTab(val tab: GalleryDetailState.Tab) : GalleryDetailIntent - - data class CopyToClipboard(val content: CharSequence) : GalleryDetailIntent - - enum class SendTo : GalleryDetailIntent { - Img2Img, Txt2Img; - } - - enum class Export : GalleryDetailIntent { - Image, Params; - } - - enum class Delete : GalleryDetailIntent { - Request, Confirm - } - - data object ToggleVisibility : GalleryDetailIntent - - data object Report : GalleryDetailIntent - - data object DismissDialog : GalleryDetailIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt deleted file mode 100644 index 16a0224c2..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt +++ /dev/null @@ -1,508 +0,0 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - -package com.shifthackz.aisdv1.presentation.screen.gallery.detail - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowBack -import androidx.compose.material.icons.filled.ContentCopy -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Report -import androidx.compose.material.icons.filled.Share -import androidx.compose.material.icons.filled.Visibility -import androidx.compose.material.icons.filled.VisibilityOff -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.NavigationBarItemDefaults -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.sharing.shareFile -import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.theme.colors -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.aisdv1.presentation.widget.image.ZoomableImage -import com.shifthackz.aisdv1.presentation.widget.image.ZoomableImageSource -import com.shifthackz.catppuccin.palette.Catppuccin -import org.koin.androidx.compose.koinViewModel -import org.koin.compose.koinInject -import org.koin.core.parameter.parametersOf -import com.shifthackz.aisdv1.core.localization.R as LocalizationR -import com.shifthackz.aisdv1.presentation.R as PresentationR - -@Composable -fun GalleryDetailScreen(itemId: Long) { - val context = LocalContext.current - val clipboardManager = LocalClipboardManager.current - val fileProviderDescriptor: FileProviderDescriptor = koinInject() - val galleryDetailSharing: GalleryDetailSharing = koinInject() - MviComponent( - viewModel = koinViewModel( - parameters = { parametersOf(itemId) }, - ), - processEffect = { effect -> - when (effect) { - is GalleryDetailEffect.ShareImageFile -> context.shareFile( - file = effect.file, - fileProviderPath = fileProviderDescriptor.providerPath, - fileMimeType = Constants.MIME_TYPE_JPG, - ) - - is GalleryDetailEffect.ShareGenerationParams -> galleryDetailSharing( - context = context, - state = effect.state, - ) - - is GalleryDetailEffect.ShareClipBoard -> { - clipboardManager.setText(AnnotatedString(effect.text)) - } - } - }, - ) { state, intentHandler -> - ScreenContent( - modifier = Modifier.fillMaxSize(), - state = state, - processIntent = intentHandler, - ) - } -} - -@Composable -private fun ScreenContent( - modifier: Modifier = Modifier, - state: GalleryDetailState, - processIntent: (GalleryDetailIntent) -> Unit = {}, -) { - Box(modifier = modifier) { - Scaffold( - modifier = Modifier.fillMaxSize(), - topBar = { - CenterAlignedTopAppBar( - title = { - Text(stringResource(id = LocalizationR.string.title_gallery_details)) - }, - navigationIcon = { - IconButton( - onClick = { - processIntent(GalleryDetailIntent.NavigateBack) - }, - content = { - Icon( - Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = "Back button", - ) - }, - ) - }, - actions = { - AnimatedVisibility( - visible = state.selectedTab != GalleryDetailState.Tab.INFO, - enter = fadeIn(), - exit = fadeOut(), - ) { - IconButton( - onClick = { processIntent(GalleryDetailIntent.Export.Image) }, - content = { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(id = PresentationR.drawable.ic_share), - contentDescription = "Export", - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - }, - ) - } - } - ) - }, - content = { paddingValues -> - val contentModifier = Modifier - .fillMaxSize() - .padding(paddingValues) - - when (state) { - is GalleryDetailState.Content -> GalleryDetailContentState( - modifier = contentModifier, - state = state, - onCopyTextClick = { - processIntent(GalleryDetailIntent.CopyToClipboard(it)) - }, - ) - - is GalleryDetailState.Loading -> Unit - } - }, - bottomBar = { - GalleryDetailNavigationBar( - state = state, - processIntent = processIntent, - ) - }, - ) - ModalRenderer(screenModal = state.screenModal) { - (it as? GalleryDetailIntent)?.let(processIntent::invoke) - } - } -} - -@Composable -private fun GalleryDetailNavigationBar( - state: GalleryDetailState, - processIntent: (GalleryDetailIntent) -> Unit = {}, -) { - Column( - modifier = Modifier - .background(color = MaterialTheme.colorScheme.surface), - ) { - if (state is GalleryDetailState.Content) { - if (state.showReportButton) { - OutlinedButton( - modifier = Modifier - .padding(horizontal = 24.dp, vertical = 4.dp) - .fillMaxWidth() - .align(Alignment.CenterHorizontally), - onClick = { processIntent(GalleryDetailIntent.Report) }, - ) { - Icon( - modifier = Modifier.padding(end = 8.dp), - imageVector = Icons.Default.Report, - contentDescription = "Report", - ) - Text( - text = stringResource(LocalizationR.string.report_title), - color = LocalContentColor.current - ) - } - } - Row( - modifier = Modifier - .background(color = MaterialTheme.colorScheme.surface) - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 2.dp), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - IconButton( - onClick = { processIntent(GalleryDetailIntent.SendTo.Txt2Img) }, - ) { - Icon( - modifier = Modifier.size(24.dp), - painter = painterResource(id = PresentationR.drawable.ic_text), - contentDescription = "txt2img", - tint = LocalContentColor.current, - ) - } - IconButton( - onClick = { processIntent(GalleryDetailIntent.SendTo.Img2Img) }, - ) { - Icon( - modifier = Modifier.size(24.dp), - painter = painterResource(id = PresentationR.drawable.ic_image), - contentDescription = "img2img", - tint = LocalContentColor.current, - ) - } - IconButton( - onClick = { processIntent(GalleryDetailIntent.ToggleVisibility) }, - ) { - Icon( - imageVector = if (state.hidden) { - Icons.Default.VisibilityOff - } else { - Icons.Default.Visibility - }, - contentDescription = "Toggle visibility", - ) - } - IconButton( - onClick = { processIntent(GalleryDetailIntent.Export.Params) }, - ) { - Icon( - imageVector = Icons.Default.Share, - contentDescription = "Share prompt", - ) - } - IconButton( - onClick = { processIntent(GalleryDetailIntent.Delete.Request) }, - ) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Delete", - ) - } - } - } - NavigationBar( - containerColor = MaterialTheme.colorScheme.surface, - ) { - state.tabs.forEach { tab -> - NavigationBarItem( - selected = state.selectedTab == tab, - label = { - Text( - text = stringResource(id = tab.label), - color = LocalContentColor.current, - ) - }, - colors = NavigationBarItemDefaults.colors().copy( - selectedIndicatorColor = MaterialTheme.colorScheme.primary, - ), - icon = { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(tab.iconRes), - contentDescription = stringResource(id = LocalizationR.string.gallery_tab_image), - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - }, - onClick = { processIntent(GalleryDetailIntent.SelectTab(tab)) }, - ) - } - } - } - -} - -@Composable -private fun GalleryDetailContentState( - modifier: Modifier = Modifier, - state: GalleryDetailState.Content, - onCopyTextClick: (CharSequence) -> Unit = {}, -) { - Column( - modifier = modifier, - ) { - when (state.selectedTab) { - GalleryDetailState.Tab.IMAGE -> ZoomableImage( - modifier = Modifier.fillMaxSize(), - source = ZoomableImageSource.Bmp(state.bitmap), - hideImage = state.hidden, - ) - - GalleryDetailState.Tab.ORIGINAL -> state.inputBitmap?.let { bmp -> - ZoomableImage( - modifier = Modifier.fillMaxSize(), - source = ZoomableImageSource.Bmp(bmp), - ) - } - - GalleryDetailState.Tab.INFO -> GalleryDetailsTable( - modifier = Modifier.fillMaxSize(), - state = state, - onCopyTextClick = onCopyTextClick, - ) - } - } -} - -@Composable -private fun GalleryDetailsTable( - modifier: Modifier = Modifier, - state: GalleryDetailState.Content, - onCopyTextClick: (CharSequence) -> Unit = {}, -) { - Column( - modifier = modifier - .verticalScroll(rememberScrollState()) - .fillMaxSize(), - ) { - val colorOddBg = MaterialTheme.colorScheme.surface - val colorOddText = colors( - light = Catppuccin.Latte.Text, - dark = Catppuccin.Frappe.Text - ) - val colorEvenBg = MaterialTheme.colorScheme.surfaceTint - GalleryDetailRow( - modifier = Modifier.background(color = colorOddBg), - name = LocalizationR.string.gallery_info_field_date.asUiText(), - value = state.createdAt, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorEvenBg), - name = LocalizationR.string.gallery_info_field_type.asUiText(), - value = state.type, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorOddBg), - name = LocalizationR.string.gallery_info_field_prompt.asUiText(), - value = state.prompt, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorEvenBg), - name = LocalizationR.string.gallery_info_field_negative_prompt.asUiText(), - value = state.negativePrompt, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorOddBg), - name = LocalizationR.string.gallery_info_field_size.asUiText(), - value = state.size, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorEvenBg), - name = LocalizationR.string.gallery_info_field_sampling_steps.asUiText(), - value = state.samplingSteps, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorOddBg), - name = LocalizationR.string.gallery_info_field_cfg.asUiText(), - value = state.cfgScale, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorEvenBg), - name = LocalizationR.string.gallery_info_field_restore_faces.asUiText(), - value = state.restoreFaces, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorOddBg), - name = LocalizationR.string.gallery_info_field_sampler.asUiText(), - value = state.sampler, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorEvenBg), - name = LocalizationR.string.gallery_info_field_seed.asUiText(), - value = state.seed, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorOddBg), - name = LocalizationR.string.gallery_info_field_sub_seed.asUiText(), - value = state.subSeed, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - GalleryDetailRow( - modifier = Modifier.background(color = colorEvenBg), - name = LocalizationR.string.gallery_info_field_sub_seed_strength.asUiText(), - value = state.subSeedStrength, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - if (state.generationType == AiGenerationResult.Type.IMAGE_TO_IMAGE) GalleryDetailRow( - modifier = Modifier.background(color = colorOddBg), - name = LocalizationR.string.gallery_info_field_denoising_strength.asUiText(), - value = state.denoisingStrength, - color = colorOddText, - onCopyTextClick = onCopyTextClick, - ) - } -} - -@Composable -private fun GalleryDetailRow( - modifier: Modifier = Modifier, - column1Weight: Float = 0.4f, - column2Weight: Float = 0.6f, - name: UiText, - value: UiText, - color: Color, - onCopyTextClick: (CharSequence) -> Unit = {}, -) { - val rawValue = value.asString() - Row(modifier) { - GalleryDetailCell( - text = name, - modifier = Modifier.weight(column1Weight), - color = color, - ) - GalleryDetailCell( - text = value, - modifier = Modifier.weight(column2Weight), - color = color, - ) - if (rawValue.isNotBlank()) { - IconButton( - onClick = { onCopyTextClick(rawValue) }, - ) { - Icon( - modifier = Modifier.size(20.dp), - imageVector = Icons.Default.ContentCopy, - contentDescription = "Copy", - tint = MaterialTheme.colorScheme.primary, - ) - } - } - } -} - -@Composable -private fun GalleryDetailCell( - modifier: Modifier = Modifier, - text: UiText, - color: Color, -) { - Text( - modifier = modifier - .padding(start = 12.dp) - .padding(vertical = 8.dp), - text = text.asString(), - color = color, - ) -} - -@Composable -@Preview(showSystemUi = true, showBackground = true) -private fun PreviewGalleryScreenTxt2ImgContentTabImage() { - ScreenContent(state = mockGalleryDetailTxt2Img) -} - -@Composable -@Preview(showSystemUi = true, showBackground = true) -private fun PreviewGalleryScreenTxt2ImgContentTabInfo() { - ScreenContent(state = mockGalleryDetailTxt2Img.copy(selectedTab = GalleryDetailState.Tab.INFO)) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailState.kt deleted file mode 100644 index 9db5c925b..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailState.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.detail - -import android.graphics.Bitmap -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.presentation.extensions.mapToUi -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.android.core.mvi.MviState -import com.shifthackz.aisdv1.core.localization.R as LocalizationR -import com.shifthackz.aisdv1.presentation.R as PresentationR - -sealed interface GalleryDetailState : MviState { - val tabs: List - val selectedTab: Tab - val screenModal: Modal - - @Immutable - data class Loading( - override val tabs: List = emptyList(), - override val selectedTab: Tab = Tab.IMAGE, - override val screenModal: Modal = Modal.None, - ) : GalleryDetailState - - @Immutable - data class Content( - override val tabs: List = emptyList(), - override val selectedTab: Tab = Tab.IMAGE, - override val screenModal: Modal = Modal.None, - val showReportButton: Boolean = false, - val generationType: AiGenerationResult.Type, - val id: Long, - val bitmap: Bitmap, - val inputBitmap: Bitmap?, - val createdAt: UiText, - val type: UiText, - val prompt: UiText, - val negativePrompt: UiText, - val size: UiText, - val samplingSteps: UiText, - val cfgScale: UiText, - val restoreFaces: UiText, - val sampler: UiText, - val seed: UiText, - val subSeed: UiText, - val subSeedStrength: UiText, - val denoisingStrength: UiText, - val hidden: Boolean, - ) : GalleryDetailState - - fun withTab(tab: Tab): GalleryDetailState = when (this) { - is Content -> copy(selectedTab = tab) - is Loading -> copy(selectedTab = tab) - } - - fun withDialog(dialog: Modal) = when (this) { - is Content -> copy(screenModal = dialog) - is Loading -> copy(screenModal = dialog) - } - - fun withHiddenState(value: Boolean) = when (this) { - is Content -> copy(hidden = value) - is Loading -> this - } - - enum class Tab( - @StringRes val label: Int, - @DrawableRes val iconRes: Int, - ) { - IMAGE(LocalizationR.string.gallery_tab_image, PresentationR.drawable.ic_image), - ORIGINAL(LocalizationR.string.gallery_tab_original, PresentationR.drawable.ic_image), - INFO(LocalizationR.string.gallery_tab_info, PresentationR.drawable.ic_text); - - companion object { - fun consume(type: AiGenerationResult.Type): List = when (type) { - AiGenerationResult.Type.TEXT_TO_IMAGE -> listOf( - IMAGE, INFO, - ) - - AiGenerationResult.Type.IMAGE_TO_IMAGE -> entries - } - } - } -} - -fun Triple.mapToUi(): GalleryDetailState.Content = - let { (ai, out, original) -> - GalleryDetailState.Content( - tabs = GalleryDetailState.Tab.consume(ai.type), - generationType = ai.type, - id = ai.id, - bitmap = out.bitmap, - inputBitmap = original?.bitmap, - createdAt = ai.createdAt.toString().asUiText(), - type = ai.type.key.asUiText(), - prompt = ai.prompt.asUiText(), - negativePrompt = ai.negativePrompt.asUiText(), - size = "${ai.width} X ${ai.height}".asUiText(), - samplingSteps = ai.samplingSteps.toString().asUiText(), - cfgScale = ai.cfgScale.toString().asUiText(), - restoreFaces = ai.restoreFaces.mapToUi(), - sampler = ai.sampler.asUiText(), - seed = ai.seed.asUiText(), - subSeed = ai.subSeed.asUiText(), - subSeedStrength = ai.subSeedStrength.toString().asUiText(), - denoisingStrength = ai.denoisingStrength.toString().asUiText(), - hidden = ai.hidden, - ) - } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailViewModel.kt deleted file mode 100644 index bf4d9cbe9..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailViewModel.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.detail - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Input -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.usecase.caching.GetLastResultFromCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.ToggleImageVisibilityUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultUseCase -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.kotlin.subscribeBy - -class GalleryDetailViewModel( - private val itemId: Long, - dispatchersProvider: DispatchersProvider, - private val buildInfoProvider: BuildInfoProvider, - private val getGenerationResultUseCase: GetGenerationResultUseCase, - private val getLastResultFromCacheUseCase: GetLastResultFromCacheUseCase, - private val deleteGalleryItemUseCase: DeleteGalleryItemUseCase, - private val toggleImageVisibilityUseCase: ToggleImageVisibilityUseCase, - private val galleryDetailBitmapExporter: GalleryDetailBitmapExporter, - private val base64ToBitmapConverter: Base64ToBitmapConverter, - private val schedulersProvider: SchedulersProvider, - private val generationFormUpdateEvent: GenerationFormUpdateEvent, - private val mainRouter: MainRouter, -) : MviRxViewModel() { - - override val initialState = GalleryDetailState.Loading() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !getGenerationResult(itemId) - .subscribeOnMainThread(schedulersProvider) - .postProcess() - .subscribeBy(::errorLog) { ai -> - updateState { - ai.mapToUi() - .copy(showReportButton = buildInfoProvider.type != BuildType.FOSS) - .withTab(currentState.selectedTab) - } - } - } - - override fun processIntent(intent: GalleryDetailIntent) { - when (intent) { - is GalleryDetailIntent.CopyToClipboard -> { - emitEffect(GalleryDetailEffect.ShareClipBoard(intent.content.toString())) - } - - GalleryDetailIntent.Delete.Request -> setActiveModal( - Modal.DeleteImageConfirm(false, isMultiple = false) - ) - - GalleryDetailIntent.Delete.Confirm -> { - setActiveModal(Modal.None) - delete() - } - - GalleryDetailIntent.Export.Image -> share() - - GalleryDetailIntent.Export.Params -> { - emitEffect(GalleryDetailEffect.ShareGenerationParams(currentState)) - } - - GalleryDetailIntent.NavigateBack -> mainRouter.navigateBack() - - is GalleryDetailIntent.SelectTab -> updateState { - it.withTab(intent.tab) - } - - GalleryDetailIntent.SendTo.Txt2Img -> sendPromptToGenerationScreen( - AiGenerationResult.Type.TEXT_TO_IMAGE, - ) - - GalleryDetailIntent.SendTo.Img2Img -> sendPromptToGenerationScreen( - AiGenerationResult.Type.IMAGE_TO_IMAGE, - ) - - GalleryDetailIntent.DismissDialog -> setActiveModal(Modal.None) - - GalleryDetailIntent.Report -> (currentState as? GalleryDetailState.Content) - ?.id - ?.let(mainRouter::navigateToReportImage) - - GalleryDetailIntent.ToggleVisibility -> toggleVisibility() - } - } - - private fun share() { - val state = currentState as? GalleryDetailState.Content ?: return - val bitmap = if ( - state.generationType == AiGenerationResult.Type.IMAGE_TO_IMAGE - && state.inputBitmap != null - && state.selectedTab == GalleryDetailState.Tab.ORIGINAL - ) { - state.inputBitmap - } else { - state.bitmap - } - !galleryDetailBitmapExporter(bitmap) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { file -> - emitEffect(GalleryDetailEffect.ShareImageFile(file)) - } - } - - private fun delete() { - val state = currentState as? GalleryDetailState.Content ?: return - !deleteGalleryItemUseCase(state.id) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { mainRouter.navigateBack() } - } - - private fun setActiveModal(dialog: Modal) = updateState { - it.withDialog(dialog) - } - - private fun Single.postProcess() = this - .flatMap { ai -> - base64ToBitmapConverter(Input(ai.image)).map { bmp -> ai to bmp } - } - .flatMap { (ai, bmp) -> - when (ai.type) { - AiGenerationResult.Type.TEXT_TO_IMAGE -> Single.just(Triple(ai, bmp, null)) - AiGenerationResult.Type.IMAGE_TO_IMAGE -> - base64ToBitmapConverter(Input(ai.inputImage)).map { bmp2 -> - Triple(ai, bmp, bmp2) - } - } - } - - private fun sendPromptToGenerationScreen(screenType: AiGenerationResult.Type) { - val state = (currentState as? GalleryDetailState.Content) ?: return - !getGenerationResult(itemId) - .subscribeOnMainThread(schedulersProvider) - .doFinally { mainRouter.navigateBack() } - .subscribeBy(::errorLog) { ai -> - generationFormUpdateEvent.update( - ai, - screenType, - state.selectedTab == GalleryDetailState.Tab.ORIGINAL, - ) - } - - } - - private fun toggleVisibility() = !toggleImageVisibilityUseCase(itemId) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { hidden -> - updateState { it.withHiddenState(hidden) } - } - - private fun getGenerationResult(id: Long): Single { - if (id <= 0) return getLastResultFromCacheUseCase() - return getGenerationResultUseCase(id) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryEffect.kt deleted file mode 100644 index b3cacb1fb..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryEffect.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import android.net.Uri -import com.shifthackz.android.core.mvi.MviEffect -import java.io.File - -sealed interface GalleryEffect : MviEffect { - - data object Refresh : GalleryEffect - - data class Share(val zipFile: File) : GalleryEffect - - data class OpenUri(val uri: Uri) : GalleryEffect -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryExporter.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryExporter.kt deleted file mode 100644 index bbdd70e45..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryExporter.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Input -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Output -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.usecase.gallery.GetAllGalleryUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.GetGalleryItemsUseCase -import com.shifthackz.aisdv1.presentation.utils.FileSavableExporter -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single -import java.io.File - -class GalleryExporter( - override val fileProviderDescriptor: FileProviderDescriptor, - private val getGalleryItemsUseCase: GetGalleryItemsUseCase, - private val getAllGalleryUseCase: GetAllGalleryUseCase, - private val base64ToBitmapConverter: Base64ToBitmapConverter, - private val schedulersProvider: SchedulersProvider, -) : FileSavableExporter.BmpToFile, FileSavableExporter.FilesToZip { - - operator fun invoke(ids: List? = null): Single { - val chain = ids?.let(getGalleryItemsUseCase::invoke) ?: getAllGalleryUseCase() - return chain - .subscribeOn(schedulersProvider.io) - .flatMapObservable { Observable.fromIterable(it) } - .map { aiDomain -> aiDomain to Input(aiDomain.image) } - .flatMapSingle { (aiDomain, input) -> - base64ToBitmapConverter(input).map { out -> aiDomain to out } - } - .flatMapSingle(::saveBitmapToFileImpl) - .toList() - .flatMap(::saveFilesToZip) - } - - private fun saveBitmapToFileImpl(data: Pair) = - saveBitmapToFile(data.first.hashCode().toString(), data.second.bitmap) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryIntent.kt deleted file mode 100644 index 449a0b19c..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryIntent.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import android.net.Uri -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface GalleryIntent : MviIntent { - - sealed interface Export : GalleryIntent { - - enum class All : Export { - Request, Confirm; - } - - enum class Selection : Export { - Request, Confirm; - } - } - - sealed interface Delete : GalleryIntent { - - enum class All : Export { - Request, Confirm; - } - - enum class Selection : Delete { - Request, Confirm; - } - } - - enum class Dropdown : GalleryIntent { - Toggle, Show, Close; - } - - data object DismissDialog : GalleryIntent - - data class OpenItem(val item: GalleryGridItemUi) : GalleryIntent - - data class OpenMediaStoreFolder(val uri: Uri) : GalleryIntent - - data class Drawer(val intent: DrawerIntent) : GalleryIntent - - data class ChangeSelectionMode(val flag: Boolean) : GalleryIntent - - data object UnselectAll : GalleryIntent - - data class ToggleItemSelection(val id: Long) : GalleryIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryPagingSource.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryPagingSource.kt deleted file mode 100644 index ffcc80124..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryPagingSource.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import androidx.paging.PagingSource -import androidx.paging.PagingState -import androidx.paging.rxjava3.RxPagingSource -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Input -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Output -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase -import com.shifthackz.aisdv1.presentation.utils.Constants -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single - -typealias GalleryPagedResult = PagingSource.LoadResult - -class GalleryPagingSource( - private val getGenerationResultPagedUseCase: GetGenerationResultPagedUseCase, - private val base64ToBitmapConverter: Base64ToBitmapConverter, - private val schedulersProvider: SchedulersProvider, -) : RxPagingSource() { - - override fun getRefreshKey(state: PagingState) = FIRST_KEY - - override fun loadSingle(params: LoadParams) = loadSingleImpl(params) - - private fun loadSingleImpl(params: LoadParams): Single { - val pageSize = params.loadSize - val pageNext = params.key ?: FIRST_KEY - return getGenerationResultPagedUseCase( - limit = pageSize, - offset = pageNext * Constants.PAGINATION_PAYLOAD_SIZE, - ) - .subscribeOn(schedulersProvider.computation) - .flatMapObservable { Observable.fromIterable(it) } - .map { ai -> Triple(ai.id, ai.hidden, ai.image) } - .map { (id, hidden, base64) -> Triple(id, hidden, Input(base64)) } - .concatMapSingle { (id, hidden, input) -> - base64ToBitmapConverter(input).map { out -> Triple(id, hidden, out) } - } - .map(::mapOutputToUi) - .toList() - .map { payload -> - LoadResult.Page( - data = payload, - prevKey = if (pageNext == FIRST_KEY) null else pageNext - 1, - nextKey = if (payload.isEmpty()) null else pageNext + 1, - ).let(GalleryPagingSource::Wrapper) - } - .onErrorReturn { t -> - errorLog(t) - Wrapper(LoadResult.Error(t)) - } - .map(Wrapper::loadResult) - } - - private fun mapOutputToUi(output: Triple) = GalleryGridItemUi( - output.first, - output.third.bitmap, - output.second, - ) - - private data class Wrapper(val loadResult: GalleryPagedResult) - - companion object { - const val FIRST_KEY = 0 - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryScreen.kt deleted file mode 100644 index ce4161914..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryScreen.kt +++ /dev/null @@ -1,634 +0,0 @@ -@file:OptIn( - ExperimentalMaterial3Api::class, - ExperimentalFoundationApi::class, -) - -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import android.content.Intent -import android.provider.DocumentsContract -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -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.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Checklist -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.FileOpen -import androidx.compose.material.icons.filled.Menu -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material.icons.filled.VisibilityOff -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.LocalMinimumInteractiveComponentSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.BlurEffect -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.toUpperCase -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.paging.LoadState -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.collectAsLazyPagingItems -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.extensions.items -import com.shifthackz.aisdv1.core.extensions.shake -import com.shifthackz.aisdv1.core.extensions.shimmer -import com.shifthackz.aisdv1.core.sharing.shareFile -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.presentation.R -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.aisdv1.presentation.widget.work.BackgroundWorkWidget -import com.shifthackz.android.core.mvi.MviComponent -import org.koin.androidx.compose.koinViewModel -import org.koin.compose.koinInject -import kotlin.random.Random -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun GalleryScreen() { - val viewModel = koinViewModel() - val context = LocalContext.current - val fileProviderDescriptor: FileProviderDescriptor = koinInject() - val pagingFlow = viewModel.pagingFlow - val lazyGalleryItems = pagingFlow.collectAsLazyPagingItems() - MviComponent( - viewModel = viewModel, - processEffect = { effect -> - when (effect) { - is GalleryEffect.OpenUri -> with(Intent(Intent.ACTION_VIEW)) { - setDataAndType(effect.uri, DocumentsContract.Document.MIME_TYPE_DIR) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - context.startActivity(this) - } - - is GalleryEffect.Share -> context.shareFile( - file = effect.zipFile, - fileProviderPath = fileProviderDescriptor.providerPath, - fileMimeType = Constants.MIME_TYPE_ZIP, - ) - - GalleryEffect.Refresh -> { - lazyGalleryItems.refresh() - } - } - }, - ) { state, intentHandler -> - BackHandler(state.selectionMode) { - intentHandler(GalleryIntent.ChangeSelectionMode(false)) - } - GalleryScreenContent( - state = state, - lazyGalleryItems = lazyGalleryItems, - processIntent = intentHandler, - ) - } -} - -@Composable -fun GalleryScreenContent( - modifier: Modifier = Modifier, - state: GalleryState, - lazyGalleryItems: LazyPagingItems, - processIntent: (GalleryIntent) -> Unit = {}, -) { - val listState = rememberLazyGridState() - - val emptyStatePredicate: () -> Boolean = { - lazyGalleryItems.loadState.refresh is LoadState.NotLoading - && lazyGalleryItems.itemCount == 0 - && lazyGalleryItems.loadState.append.endOfPaginationReached - } - - Box(modifier) { - Scaffold( - modifier = Modifier.fillMaxSize(), - topBar = { - Column { - CenterAlignedTopAppBar( - navigationIcon = { - AnimatedContent( - targetState = state.selectionMode, - transitionSpec = { fadeIn() togetherWith fadeOut() }, - label = "main_nav_icon_animation", - ) { isInSelectionMode -> - IconButton( - onClick = { - val intent = if (isInSelectionMode) { - GalleryIntent.ChangeSelectionMode(false) - } else { - GalleryIntent.Drawer(DrawerIntent.Open) - } - processIntent(intent) - }, - ) { - Icon( - imageVector = if (isInSelectionMode) { - Icons.Default.Close - } else { - Icons.Default.Menu - }, - contentDescription = if (isInSelectionMode) "Close" else "Menu", - ) - } - } - }, - title = { - Text( - text = stringResource(id = LocalizationR.string.title_gallery), - style = MaterialTheme.typography.headlineMedium, - ) - }, - actions = { - AnimatedContent( - targetState = state.selectionMode, - transitionSpec = { fadeIn() togetherWith fadeOut() }, - label = "action_nav_icon_animation", - ) { isInSelectionMode -> - if (isInSelectionMode) { - AnimatedVisibility( - visible = state.selection.isNotEmpty(), - enter = fadeIn(), - exit = fadeOut(), - ) { - Row { - IconButton( - onClick = { - processIntent(GalleryIntent.Delete.Selection.Request) - }, - ) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Delete", - ) - } - IconButton( - onClick = { - processIntent(GalleryIntent.Export.Selection.Request) - }, - ) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(id = R.drawable.ic_share), - contentDescription = "Export", - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - } - } - } - } else { - AnimatedVisibility( - visible = lazyGalleryItems.itemCount != 0, - enter = fadeIn(), - exit = fadeOut(), - ) { - IconButton( - onClick = { - processIntent(GalleryIntent.Dropdown.Toggle) - }, - ) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = "Dropdown", - ) - } - } - } - } - DropdownMenu( - expanded = state.dropdownMenuShow, - onDismissRequest = { processIntent(GalleryIntent.Dropdown.Close) }, - containerColor = MaterialTheme.colorScheme.background, - ) { - DropdownMenuItem( - leadingIcon = { - Icon( - imageVector = Icons.Default.Checklist, - contentDescription = "Dropdown", - tint = LocalContentColor.current, - ) - }, - text = { - Text( - text = stringResource( - id = LocalizationR.string.gallery_menu_selection_mode, - ), - ) - }, - onClick = { - processIntent(GalleryIntent.Dropdown.Close) - processIntent(GalleryIntent.ChangeSelectionMode(true)) - }, - ) - if (state.mediaStoreInfo.isNotEmpty) DropdownMenuItem( - leadingIcon = { - Icon( - imageVector = Icons.Default.FileOpen, - contentDescription = "Browse", - tint = LocalContentColor.current, - ) - }, - text = { - Text( - text = stringResource(id = LocalizationR.string.browse) - ) - }, - onClick = { - processIntent(GalleryIntent.Dropdown.Close) - state.mediaStoreInfo.folderUri?.let { - processIntent(GalleryIntent.OpenMediaStoreFolder(it)) - } - }, - ) - DropdownMenuItem( - leadingIcon = { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(id = R.drawable.ic_share), - contentDescription = "Export", - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - }, - text = { - Text( - text = stringResource(id = LocalizationR.string.gallery_menu_export_all) - ) - }, - onClick = { - processIntent(GalleryIntent.Dropdown.Close) - processIntent(GalleryIntent.Export.All.Request) - }, - ) - DropdownMenuItem( - leadingIcon = { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Delete", - ) - }, - text = { - Text( - text = stringResource(id = LocalizationR.string.gallery_menu_delete_all), - ) - }, - onClick = { - processIntent(GalleryIntent.Dropdown.Close) - processIntent(GalleryIntent.Delete.All.Request) - }, - ) - } - }, - windowInsets = WindowInsets(0, 0, 0, 0), - ) - BackgroundWorkWidget( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .padding(vertical = 4.dp), - ) - } - }, - bottomBar = { - AnimatedVisibility( - visible = state.selectionMode, - enter = fadeIn(), - exit = fadeOut(), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceTint), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - modifier = Modifier - .padding(start = 16.dp), - text = stringResource( - id = LocalizationR.string.gallery_menu_selected, - "${state.selection.size}", - ), - style = MaterialTheme.typography.bodyLarge, - lineHeight = 17.sp, - fontWeight = FontWeight.W400, - ) - Spacer(modifier = Modifier.weight(1f)) - TextButton( - modifier = Modifier.padding(end = 16.dp), - onClick = { - if (state.selection.isNotEmpty()) { - processIntent(GalleryIntent.UnselectAll) - } else { - processIntent(GalleryIntent.ChangeSelectionMode(false)) - } - }, - ) { - val resId = if (state.selection.isNotEmpty()) { - LocalizationR.string.gallery_menu_unselect_all - } else { - LocalizationR.string.cancel - } - Text( - text = stringResource(resId).toUpperCase(Locale.current), - textAlign = TextAlign.Center, - color = LocalContentColor.current, - ) - } - } - } - AnimatedVisibility( - visible = state.mediaStoreInfo.isNotEmpty && !state.selectionMode, - enter = fadeIn(), - exit = fadeOut(), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceTint), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - modifier = Modifier - .padding(start = 16.dp) - .fillMaxWidth(0.65f), - text = stringResource( - id = LocalizationR.string.gallery_media_store_banner, - "${state.mediaStoreInfo.count}", - ), - style = MaterialTheme.typography.bodyLarge, - lineHeight = 17.sp, - fontWeight = FontWeight.W400, - ) - Spacer(modifier = Modifier.weight(1f)) - TextButton( - modifier = Modifier.padding(end = 16.dp), - onClick = { - state.mediaStoreInfo.folderUri?.let { - processIntent(GalleryIntent.OpenMediaStoreFolder(it)) - } - }, - ) { - Text( - text = stringResource(id = LocalizationR.string.browse) - .toUpperCase(Locale.current), - color = LocalContentColor.current, - ) - } - } - } - }, - ) { paddingValues -> - when { - emptyStatePredicate() -> GalleryEmptyState(Modifier.fillMaxSize()) - - lazyGalleryItems.itemCount == 0 -> LazyVerticalGrid( - modifier = Modifier - .fillMaxSize() - .padding(top = paddingValues.calculateTopPadding()), - columns = GridCells.Fixed(state.grid.size), - contentPadding = PaddingValues(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - val max = when (state.grid) { - Grid.Fixed2 -> 6 - Grid.Fixed3 -> 12 - Grid.Fixed4 -> 20 - Grid.Fixed5 -> 30 - } - repeat(max) { - item(it) { - GalleryUiItemShimmer() - } - } - } - - else -> LazyVerticalGrid( - modifier = Modifier - .fillMaxSize() - .padding(top = paddingValues.calculateTopPadding()), - columns = GridCells.Fixed(state.grid.size), - contentPadding = PaddingValues(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - state = listState, - ) { - items(lazyGalleryItems) { item -> - if (item != null) { - val selected = state.selection.contains(item.id) - GalleryUiItem( - modifier = Modifier - .animateItem(tween(500)) - .shake( - enabled = state.selectionMode && !selected, - animationDurationMillis = 188, - animationStartOffset = Random.nextInt(0, 320), - ), - item = item, - selectionMode = state.selectionMode, - checked = selected, - onCheckedChange = { - processIntent(GalleryIntent.ToggleItemSelection(item.id)) - }, - onLongClick = { - processIntent(GalleryIntent.ChangeSelectionMode(true)) - processIntent(GalleryIntent.ToggleItemSelection(item.id)) - }, - onClick = { - processIntent(GalleryIntent.OpenItem(it)) - }, - ) - } else { - GalleryUiItemShimmer() - } - } - items(2) { Spacer(modifier = Modifier.height(32.dp)) } - } - } - } - ModalRenderer(screenModal = state.screenModal) { - (it as? GalleryIntent)?.let(processIntent::invoke) - } - } -} - -@Composable -fun GalleryUiItem( - modifier: Modifier = Modifier, - item: GalleryGridItemUi, - checked: Boolean = false, - onClick: (GalleryGridItemUi) -> Unit = {}, - onLongClick: () -> Unit = {}, - onCheckedChange: (Boolean) -> Unit = {}, - selectionMode: Boolean = false, -) { - val shape = RoundedCornerShape(12.dp) - val borderColor by animateColorAsState( - targetValue = if (selectionMode && checked) { - MaterialTheme.colorScheme.primary - } else { - Color.Transparent - }, - label = "border_color", - ) - Box( - modifier = modifier.clip(shape), - ) { - Image( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f) - .border( - width = 4.dp, - color = borderColor, - shape = shape, - ) - .then( - if (!item.hidden) Modifier - else Modifier.graphicsLayer { - renderEffect = BlurEffect( - radiusX = 100f, - radiusY = 100f, - ) - } - ) - .combinedClickable( - onLongClick = if (!selectionMode) onLongClick else null, - onClick = { - if (!selectionMode) { - onClick(item) - } else { - onCheckedChange(!checked) - } - }, - ), - bitmap = item.bitmap.asImageBitmap(), - contentScale = ContentScale.Crop, - contentDescription = "gallery_item", - ) - if (item.hidden) { - Icon( - modifier = Modifier - .size(28.dp) - .align(Alignment.Center), - imageVector = Icons.Default.VisibilityOff, - contentDescription = "hidden", - tint = MaterialTheme.colorScheme.primary, - ) - } - if (selectionMode) { - val checkBoxShape = RoundedCornerShape(4.dp) - Box( - modifier = Modifier - .padding(8.dp) - .size(20.dp) - .align(Alignment.TopEnd) - .clip(checkBoxShape) - .background( - color = MaterialTheme.colorScheme.primary.copy(alpha = 0.35f), - shape = checkBoxShape, - ), - ) { - CompositionLocalProvider( - LocalMinimumInteractiveComponentSize provides Dp.Unspecified, - ) { - Checkbox( - checked = checked, - onCheckedChange = onCheckedChange, - colors = CheckboxDefaults.colors( - uncheckedColor = MaterialTheme.colorScheme.primary, - ), - ) - } - } - } - } -} - -@Composable -private fun GalleryUiItemShimmer() { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f) - .clip(RoundedCornerShape(12.dp)) - .shimmer() - ) -} - -@Composable -private fun GalleryEmptyState(modifier: Modifier = Modifier) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.Center, - ) { - Text( - modifier = Modifier.align(Alignment.CenterHorizontally), - text = stringResource(id = LocalizationR.string.gallery_empty_title), - fontSize = 20.sp, - ) - Text( - modifier = Modifier - .padding(top = 16.dp) - .padding(horizontal = 16.dp) - .align(Alignment.CenterHorizontally), - text = stringResource(id = LocalizationR.string.gallery_empty_sub_title), - textAlign = TextAlign.Center, - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryState.kt deleted file mode 100644 index 98420bb0b..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryState.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import android.graphics.Bitmap -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.android.core.mvi.MviState - -@Immutable -data class GalleryState( - val screenModal: Modal = Modal.None, - val mediaStoreInfo: MediaStoreInfo = MediaStoreInfo(), - val dropdownMenuShow: Boolean = false, - val selectionMode: Boolean = false, - val selection: List = emptyList(), - val grid: Grid = Grid.Fixed2, -) : MviState - -data class GalleryGridItemUi( - val id: Long, - val bitmap: Bitmap, - val hidden: Boolean, -) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryViewModel.kt deleted file mode 100644 index d7b41b262..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryViewModel.kt +++ /dev/null @@ -1,197 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.BackgroundWorkResult -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteAllGalleryUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemsUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.GetMediaStoreInfoUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.utils.Constants -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.kotlin.subscribeBy -import kotlinx.coroutines.flow.Flow - -class GalleryViewModel( - dispatchersProvider: DispatchersProvider, - getMediaStoreInfoUseCase: GetMediaStoreInfoUseCase, - backgroundWorkObserver: BackgroundWorkObserver, - preferenceManager: PreferenceManager, - private val deleteAllGalleryUseCase: DeleteAllGalleryUseCase, - private val deleteGalleryItemsUseCase: DeleteGalleryItemsUseCase, - private val getGenerationResultPagedUseCase: GetGenerationResultPagedUseCase, - private val base64ToBitmapConverter: Base64ToBitmapConverter, - private val galleryExporter: GalleryExporter, - private val schedulersProvider: SchedulersProvider, - private val mainRouter: MainRouter, - private val drawerRouter: DrawerRouter, -) : MviRxViewModel() { - - override val initialState = GalleryState() - - override val effectDispatcher = dispatchersProvider.immediate - - private val config = PagingConfig( - pageSize = Constants.PAGINATION_PAYLOAD_SIZE, - initialLoadSize = Constants.PAGINATION_PAYLOAD_SIZE - ) - - private val pager: Pager = Pager( - config = config, - initialKey = GalleryPagingSource.FIRST_KEY, - pagingSourceFactory = { - GalleryPagingSource( - getGenerationResultPagedUseCase, - base64ToBitmapConverter, - schedulersProvider, - ) - } - ) - - val pagingFlow: Flow> = pager.flow - - init { - !preferenceManager - .observe() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { settings -> - updateState { it.copy(grid = settings.galleryGrid) } - } - - !getMediaStoreInfoUseCase() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { info -> - updateState { it.copy(mediaStoreInfo = info) } - } - - !backgroundWorkObserver - .observeResult() - .ofType(BackgroundWorkResult.Success::class.java) - .map { GalleryEffect.Refresh } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) - } - - override fun processIntent(intent: GalleryIntent) { - when (intent) { - GalleryIntent.DismissDialog -> setActiveModal(Modal.None) - - GalleryIntent.Export.All.Request -> setActiveModal(Modal.ConfirmExport(true)) - - GalleryIntent.Export.All.Confirm -> launchGalleryExport(true) - - GalleryIntent.Export.Selection.Request -> setActiveModal(Modal.ConfirmExport(false)) - - GalleryIntent.Export.Selection.Confirm -> launchGalleryExport(false) - - is GalleryIntent.OpenItem -> mainRouter.navigateToGalleryDetails(intent.item.id) - - is GalleryIntent.OpenMediaStoreFolder -> emitEffect(GalleryEffect.OpenUri(intent.uri)) - - is GalleryIntent.Drawer -> when (intent.intent) { - DrawerIntent.Close -> drawerRouter.closeDrawer() - DrawerIntent.Open -> drawerRouter.openDrawer() - } - - is GalleryIntent.ChangeSelectionMode -> updateState { - it.copy( - selectionMode = intent.flag, - selection = if (!intent.flag) emptyList() else it.selection, - ) - } - - is GalleryIntent.ToggleItemSelection -> updateState { - val selectionIndex = it.selection.indexOf(intent.id) - val newSelection = it.selection.toMutableList() - if (selectionIndex != -1) { - newSelection.removeAt(selectionIndex) - } else { - newSelection.add(intent.id) - } - it.copy(selection = newSelection) - } - - GalleryIntent.Delete.Selection.Request -> setActiveModal( - Modal.DeleteImageConfirm(isAll = false, isMultiple = true) - ) - - GalleryIntent.Delete.Selection.Confirm -> !deleteGalleryItemsUseCase - .invoke(currentState.selection) - .processDeletion() - - GalleryIntent.Delete.All.Request -> setActiveModal( - Modal.DeleteImageConfirm(isAll = true, isMultiple = false) - ) - - GalleryIntent.Delete.All.Confirm -> !deleteAllGalleryUseCase() - .processDeletion() - - GalleryIntent.UnselectAll -> updateState { - it.copy(selection = emptyList()) - } - - GalleryIntent.Dropdown.Toggle -> updateState { - it.copy(dropdownMenuShow = !it.dropdownMenuShow) - } - - GalleryIntent.Dropdown.Show -> updateState { - it.copy(dropdownMenuShow = true) - } - - GalleryIntent.Dropdown.Close -> updateState { - it.copy(dropdownMenuShow = false) - } - } - } - - private fun Completable.processDeletion() = this - .doOnSubscribe { setActiveModal(Modal.None) } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { - updateState { - it.copy( - selectionMode = false, - selection = emptyList() - ) - } - emitEffect(GalleryEffect.Refresh) - } - - private fun launchGalleryExport(exportAll: Boolean) = !galleryExporter - .invoke(if (exportAll) null else currentState.selection) - .doOnSubscribe { setActiveModal(Modal.ExportInProgress) } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = { t -> - setActiveModal( - Modal.Error( - (t.localizedMessage ?: "Something went wrong").asUiText() - ) - ) - errorLog(t) - }, - onSuccess = { zipFile -> - setActiveModal(Modal.None) - emitEffect(GalleryEffect.Share(zipFile)) - } - ) - - private fun setActiveModal(dialog: Modal) = updateState { - it.copy(screenModal = dialog, dropdownMenuShow = false) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationIntent.kt deleted file mode 100644 index 84550d55a..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationIntent.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.home - -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface HomeNavigationIntent : MviIntent { - - data class Route(val navRoute: NavigationRoute) : HomeNavigationIntent - - data class Update(val navRoute: NavigationRoute) : HomeNavigationIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationMappers.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationMappers.kt deleted file mode 100644 index 4c30dc640..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationMappers.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.home - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute - -fun AiGenerationResult.Type.mapToRoute(): NavigationRoute = when (this) { - AiGenerationResult.Type.TEXT_TO_IMAGE -> NavigationRoute.HomeNavigation.TxtToImg - AiGenerationResult.Type.IMAGE_TO_IMAGE -> NavigationRoute.HomeNavigation.ImgToImg -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationScreen.kt deleted file mode 100644 index 083880b79..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationScreen.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.home - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.NavigationBarItemDefaults -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.navigation.NavDestination.Companion.hasRoute -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.presentation.model.NavItem -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute.HomeNavigation -import com.shifthackz.aisdv1.presentation.widget.connectivity.ConnectivityComposable -import com.shifthackz.aisdv1.presentation.widget.item.NavigationItemIcon -import org.koin.androidx.compose.koinViewModel - -@Composable -fun HomeNavigationScreen( - navItems: List = emptyList(), -) { - require(navItems.isNotEmpty()) { "navItems collection must not be empty." } - - val viewModel = koinViewModel() - val navController = rememberNavController() - val backStackEntry = navController.currentBackStackEntryAsState() - val currentDestination = backStackEntry.value?.destination - - LaunchedEffect(currentDestination) { - if (currentDestination?.hasRoute(HomeNavigation.TxtToImg::class) == true) { - viewModel.processIntent(HomeNavigationIntent.Update(HomeNavigation.TxtToImg)) - } - } - - MviComponent( - viewModel = viewModel, - processEffect = { effect -> - navController.navigate(effect.navRoute) { - navController.graph.startDestinationRoute?.let { route -> - popUpTo(route) { - saveState = true - } - } - launchSingleTop = true - restoreState = true - } - }, - ) { _, processIntent -> - Scaffold( - modifier = Modifier.imePadding(), - bottomBar = { - NavigationBar( - containerColor = MaterialTheme.colorScheme.surface, - ) { - navItems.forEach { item -> - val selected = - currentDestination?.route?.contains("${item.navRoute}") == true - NavigationBarItem( - selected = selected, - label = { - Text( - text = item.name.asString(), - color = LocalContentColor.current, - ) - }, - colors = NavigationBarItemDefaults.colors().copy( - selectedIndicatorColor = MaterialTheme.colorScheme.primary, - ), - icon = { NavigationItemIcon(item.icon) }, - onClick = { processIntent(HomeNavigationIntent.Route(item.navRoute)) }, - ) - } - } - }, - content = { paddingValues -> - Column( - modifier = Modifier.padding(paddingValues), - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface), - contentAlignment = Alignment.Center, - ) { - ConnectivityComposable() - } - NavHost( - modifier = Modifier.fillMaxSize(), - navController = navController, - startDestination = navItems.first().navRoute, - ) { - navItems.forEach { item -> - when (item.navRoute as HomeNavigation) { - HomeNavigation.Gallery -> composable { - item.content?.invoke() - } - - HomeNavigation.TxtToImg -> composable { - item.content?.invoke() - } - - HomeNavigation.ImgToImg -> composable { - item.content?.invoke() - } - - HomeNavigation.Settings -> composable { - item.content?.invoke() - } - } - } - } - } - } - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationViewModel.kt deleted file mode 100644 index 429c0fd62..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/home/HomeNavigationViewModel.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.home - -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.router.home.HomeRouter -import com.shifthackz.android.core.mvi.EmptyState -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.kotlin.subscribeBy -import java.util.concurrent.TimeUnit - -class HomeNavigationViewModel( - generationFormUpdateEvent: GenerationFormUpdateEvent, - preferenceManager: PreferenceManager, - dispatchersProvider: DispatchersProvider, - schedulersProvider: SchedulersProvider, - private val homeRouter: HomeRouter, -) : MviRxViewModel() { - - override val initialState = EmptyState - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !homeRouter - .observe() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda) { effect -> - (effect as? NavigationEffect.Home.Route)?.let(::emitEffect) - } - - !generationFormUpdateEvent - .observeRoute() - .map(AiGenerationResult.Type::mapToRoute) - .map(HomeNavigationIntent::Route) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda, ::processIntent) - - !Observable - .timer(250L, TimeUnit.MILLISECONDS) - .flatMapCompletable { preferenceManager.refresh() } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) - } - - override fun processIntent(intent: HomeNavigationIntent) { - when (intent) { - is HomeNavigationIntent.Route -> homeRouter.navigateToRoute(intent.navRoute) - is HomeNavigationIntent.Update -> homeRouter.updateExternallyWithoutNavigation(intent.navRoute) - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageEffect.kt deleted file mode 100644 index 9a46b6b82..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageEffect.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.img2img - -import com.shifthackz.android.core.mvi.MviEffect - -enum class ImageToImageEffect : MviEffect { - GalleryPicker, CameraPicker; -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageState.kt deleted file mode 100644 index 3e301a66a..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageState.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.img2img - -import android.graphics.Bitmap -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.OpenAiQuality -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.OpenAiStyle -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.StabilityAiClipGuidance -import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset -import com.shifthackz.aisdv1.presentation.core.GenerationMviState -import com.shifthackz.aisdv1.presentation.model.InPaintModel -import com.shifthackz.aisdv1.presentation.model.Modal - -@Immutable -data class ImageToImageState( - val imageState: ImageState = ImageState.None, - val imageBase64: String = "", - val denoisingStrength: Float = 0.75f, - val inPaintModel: InPaintModel = InPaintModel(), - override val onBoardingDemo: Boolean = false, - override val screenModal: Modal = Modal.None, - override val mode: ServerSource = ServerSource.AUTOMATIC1111, - override val advancedToggleButtonVisible: Boolean = true, - override val advancedOptionsVisible: Boolean = false, - override val formPromptTaggedInput: Boolean = false, - override val prompt: String = "", - override val negativePrompt: String = "", - override val width: String = 512.toString(), - override val height: String = 512.toString(), - override val samplingSteps: Int = 20, - override val cfgScale: Float = 7f, - override val restoreFaces: Boolean = false, - override val seed: String = "", - override val subSeed: String = "", - override val subSeedStrength: Float = 0f, - override val selectedSampler: String = "", - override val availableSamplers: List = emptyList(), - override val selectedStylePreset: StabilityAiStylePreset = StabilityAiStylePreset.NONE, - override val selectedClipGuidancePreset: StabilityAiClipGuidance = StabilityAiClipGuidance.NONE, - override val openAiModel: OpenAiModel = OpenAiModel.DALL_E_2, - override val openAiSize: OpenAiSize = OpenAiSize.W1024_H1024, - override val openAiQuality: OpenAiQuality = OpenAiQuality.STANDARD, - override val openAiStyle: OpenAiStyle = OpenAiStyle.VIVID, - override val widthValidationError: UiText? = null, - override val heightValidationError: UiText? = null, - override val nsfw: Boolean = false, - override val batchCount: Int = 1, - override val generateButtonEnabled: Boolean = true, -) : GenerationMviState() { - - sealed interface ImageState { - - val isEmpty: Boolean - get() = this is None - - data object None : ImageState - data class Image(val bitmap: Bitmap) : ImageState - } - - override fun copyState( - onBoardingDemo: Boolean, - screenModal: Modal, - mode: ServerSource, - advancedToggleButtonVisible: Boolean, - advancedOptionsVisible: Boolean, - formPromptTaggedInput: Boolean, - prompt: String, - negativePrompt: String, - width: String, - height: String, - samplingSteps: Int, - cfgScale: Float, - restoreFaces: Boolean, - seed: String, - subSeed: String, - subSeedStrength: Float, - selectedSampler: String, - availableSamplers: List, - selectedStylePreset: StabilityAiStylePreset, - selectedClipGuidancePreset: StabilityAiClipGuidance, - openAiModel: OpenAiModel, - openAiSize: OpenAiSize, - openAiQuality: OpenAiQuality, - openAiStyle: OpenAiStyle, - widthValidationError: UiText?, - heightValidationError: UiText?, - nsfw: Boolean, - batchCount: Int, - generateButtonEnabled: Boolean, - ): GenerationMviState = copy( - onBoardingDemo = onBoardingDemo, - screenModal = screenModal, - mode = mode, - advancedToggleButtonVisible = advancedToggleButtonVisible, - advancedOptionsVisible = advancedOptionsVisible, - formPromptTaggedInput = formPromptTaggedInput, - prompt = prompt, - negativePrompt = negativePrompt, - width = width, - height = height, - samplingSteps = samplingSteps, - cfgScale = cfgScale, - restoreFaces = restoreFaces, - seed = seed, - subSeed = subSeed, - subSeedStrength = subSeedStrength, - selectedSampler = selectedSampler, - availableSamplers = availableSamplers, - selectedStylePreset = selectedStylePreset, - selectedClipGuidancePreset = selectedClipGuidancePreset, - openAiModel = openAiModel, - openAiSize = openAiSize, - openAiQuality = openAiQuality, - openAiStyle = openAiStyle, - widthValidationError = widthValidationError, - heightValidationError = heightValidationError, - nsfw = nsfw, - batchCount = batchCount, - generateButtonEnabled = generateButtonEnabled, - ) - - fun preProcessed(pair: Pair): ImageToImageState = copy( - imageBase64 = pair.first, - inPaintModel = this.inPaintModel.copy(base64 = pair.second), - ) -} - -enum class ImagePickButton { - PHOTO, CAMERA -} - -fun ImageToImageState.mapToPayload(): ImageToImagePayload = with(this) { - ImageToImagePayload( - base64Image = imageBase64, - base64MaskImage = inPaintModel.base64, - denoisingStrength = denoisingStrength, - prompt = prompt.trim(), - negativePrompt = negativePrompt.trim(), - samplingSteps = samplingSteps, - cfgScale = cfgScale, - width = width.toIntOrNull() ?: 64, - height = height.toIntOrNull() ?: 64, - restoreFaces = restoreFaces, - seed = seed.trim(), - subSeed = subSeed.trim(), - subSeedStrength = subSeedStrength, - sampler = selectedSampler, - nsfw = if (mode == ServerSource.HORDE) nsfw else false, - batchCount = batchCount, - inPaintingMaskInvert = inPaintModel.maskMode.inverse, - inPaintFullResPadding = inPaintModel.onlyMaskedPaddingPx, - inPaintingFill = inPaintModel.maskContent.fill, - inPaintFullRes = inPaintModel.inPaintArea.fullRes, - maskBlur = inPaintModel.maskBlur, - stabilityAiClipGuidance = selectedClipGuidancePreset.takeIf { mode == ServerSource.STABILITY_AI }, - stabilityAiStylePreset = selectedStylePreset.takeIf { mode == ServerSource.STABILITY_AI }, - ) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModel.kt deleted file mode 100644 index 3990116b1..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModel.kt +++ /dev/null @@ -1,267 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.img2img - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.ImageToImagePayload -import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.interactor.wakelock.WakeLockInterActor -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.caching.SaveLastResultToCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetRandomImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.SaveGenerationResultUseCase -import com.shifthackz.aisdv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.core.GenerationMviViewModel -import com.shifthackz.aisdv1.presentation.core.ImageToImageIntent -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintStateProducer -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.kotlin.subscribeBy -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -class ImageToImageViewModel( - dispatchersProvider: DispatchersProvider, - generationFormUpdateEvent: GenerationFormUpdateEvent, - getStableDiffusionSamplersUseCase: GetStableDiffusionSamplersUseCase, - observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, - saveLastResultToCacheUseCase: SaveLastResultToCacheUseCase, - saveGenerationResultUseCase: SaveGenerationResultUseCase, - interruptGenerationUseCase: InterruptGenerationUseCase, - drawerRouter: DrawerRouter, - dimensionValidator: DimensionValidator, - private val imageToImageUseCase: ImageToImageUseCase, - private val getRandomImageUseCase: GetRandomImageUseCase, - private val bitmapToBase64Converter: BitmapToBase64Converter, - private val base64ToBitmapConverter: Base64ToBitmapConverter, - private val preferenceManager: PreferenceManager, - private val schedulersProvider: SchedulersProvider, - private val notificationManager: PushNotificationManager, - private val wakeLockInterActor: WakeLockInterActor, - private val inPaintStateProducer: InPaintStateProducer, - private val mainRouter: MainRouter, - private val backgroundTaskManager: BackgroundTaskManager, - private val backgroundWorkObserver: BackgroundWorkObserver, - private val buildInfoProvider: BuildInfoProvider, -) : GenerationMviViewModel( - preferenceManager = preferenceManager, - getStableDiffusionSamplersUseCase = getStableDiffusionSamplersUseCase, - observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, - saveLastResultToCacheUseCase = saveLastResultToCacheUseCase, - saveGenerationResultUseCase = saveGenerationResultUseCase, - interruptGenerationUseCase = interruptGenerationUseCase, - mainRouter = mainRouter, - drawerRouter = drawerRouter, - dimensionValidator = dimensionValidator, - schedulersProvider = schedulersProvider, - backgroundWorkObserver = backgroundWorkObserver, -) { - - override val initialState = ImageToImageState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !generationFormUpdateEvent - .observeImg2ImgForm() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onNext = { payload -> - (payload as? GenerationFormUpdateEvent.Payload.I2IForm) - ?.let(::updateFormPreviousAiGeneration) - ?.also { generationFormUpdateEvent.clear() } - }, - ) - - !inPaintStateProducer - .observeInPaint() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { inPaint -> - updateState { it.copy(inPaintModel = inPaint) } - } - } - - override fun processIntent(intent: GenerationMviIntent) { - when (intent) { - ImageToImageIntent.ClearImageInput -> updateState { - inPaintStateProducer.updateInPaint(it.inPaintModel.clear()) - it.copy(imageState = ImageToImageState.ImageState.None) - } - - ImageToImageIntent.FetchRandomPhoto -> fetchRandomImage { - getRandomImageUseCase() - .doOnSubscribe { setActiveModal(Modal.LoadingRandomImage) } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = { t -> - setActiveModal( - Modal.Error( - UiText.Static( - t.localizedMessage ?: "Error" - ) - ) - ) - errorLog(t) - }, - onSuccess = { bitmap -> - setActiveModal(Modal.None) - updateState { state -> - inPaintStateProducer.updateInPaint(state.inPaintModel.clear()) - state.copy(imageState = ImageToImageState.ImageState.Image(bitmap)) - } - }, - ) - } - - is ImageToImageIntent.UpdateDenoisingStrength -> updateState { - it.copy(denoisingStrength = intent.value) - } - - ImageToImageIntent.Pick.Camera -> emitEffect(ImageToImageEffect.CameraPicker) - - ImageToImageIntent.Pick.Gallery -> emitEffect(ImageToImageEffect.GalleryPicker) - - is ImageToImageIntent.CropImage -> updateState { - it.copy(screenModal = Modal.Image.Crop(intent.bitmap)) - } - - is ImageToImageIntent.UpdateImage -> updateState { - it.copy( - screenModal = Modal.None, - imageState = ImageToImageState.ImageState.Image(intent.bitmap), - ) - } - - ImageToImageIntent.InPaint -> (currentState.imageState as? ImageToImageState.ImageState.Image) - ?.let { image -> inPaintStateProducer.updateBitmap(image.bitmap) } - ?.also { inPaintStateProducer.updateInPaint(currentState.inPaintModel) } - ?.also { mainRouter.navigateToInPaint() } - - else -> super.processIntent(intent) - } - } - - override fun generateDisposable() = when (currentState.imageState) { - is ImageToImageState.ImageState.Image -> generateImageToImagePayload() - .doOnSubscribe { - wakeLockInterActor.acquireWakelockUseCase() - setActiveModal(Modal.Communicating()) - } - .flatMap(imageToImageUseCase::invoke) - .doFinally { wakeLockInterActor.releaseWakeLockUseCase() } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = { t -> - notificationManager.createAndShowInstant( - LocalizationR.string.notification_fail_title.asUiText(), - LocalizationR.string.notification_fail_sub_title.asUiText(), - ) - setActiveModal( - Modal.Error( - UiText.Static( - t.localizedMessage ?: "Error" - ) - ) - ) - errorLog(t) - }, - onSuccess = { ai -> - notificationManager.createAndShowInstant( - LocalizationR.string.notification_finish_title.asUiText(), - LocalizationR.string.notification_finish_sub_title.asUiText(), - ) - setActiveModal( - Modal.Image.create( - list = ai, - autoSaveEnabled = preferenceManager.autoSaveAiResults, - reportEnabled = buildInfoProvider.type != BuildType.FOSS, - ) - ) - } - ) - - else -> Disposable.empty() - } - - override fun generateBackground() { - if (currentState.imageState !is ImageToImageState.ImageState.Image) return - !generateImageToImagePayload() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { payload -> - backgroundTaskManager.scheduleImageToImageTask(payload) - } - } - - override fun onReceivedHordeStatus(status: HordeProcessStatus) { - if (currentState.screenModal is Modal.Communicating) { - setActiveModal(Modal.Communicating(hordeProcessStatus = status)) - } - } - - override fun updateFormPreviousAiGeneration(payload: GenerationFormUpdateEvent.Payload) { - if (payload !is GenerationFormUpdateEvent.Payload.I2IForm) return - val base64 = if (payload.inputImage) payload.ai.inputImage else payload.ai.image - !base64ToBitmapConverter(Base64ToBitmapConverter.Input(base64)) - .map(Base64ToBitmapConverter.Output::bitmap) - .map(ImageToImageState.ImageState::Image) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onSuccess = { imageState -> - updateState { state -> - state.copy( - imageState = imageState, - inPaintModel = state.inPaintModel.clear(), - ) - } - } - ) - - return super.updateFormPreviousAiGeneration(payload) - } - - private fun generateImageToImagePayload(): Single = Single - .just( - Pair( - (currentState.imageState as ImageToImageState.ImageState.Image).bitmap, - currentState.inPaintModel.bitmap, - ) - ) - .flatMap { (bmp, maskBmp) -> - bitmapToBase64Converter(BitmapToBase64Converter.Input(bmp)) - .map(BitmapToBase64Converter.Output::base64ImageString) - .flatMap { base64 -> - maskBmp?.let { - bitmapToBase64Converter(BitmapToBase64Converter.Input(maskBmp)) - .map(BitmapToBase64Converter.Output::base64ImageString) - .map { maskBase64 -> base64 to maskBase64 } - } ?: run { - Single.just(base64 to "") - } - } - } - .map(currentState::preProcessed) - .map(ImageToImageState::mapToPayload) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintIntent.kt deleted file mode 100644 index 684266a43..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintIntent.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint - -import android.graphics.Bitmap -import androidx.compose.ui.graphics.Path -import com.shifthackz.aisdv1.presentation.model.InPaintModel -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface InPaintIntent : MviIntent { - - data object NavigateBack : InPaintIntent - - data class SelectTab(val tab: InPaintState.Tab) : InPaintIntent - - data class ChangeCapSize(val size: Int) : InPaintIntent - - data class DrawPath(val path: Path) : InPaintIntent - - data class DrawPathBmp(val bitmap: Bitmap?) : InPaintIntent - - enum class Action : InPaintIntent { - Undo, Clear; - } - - sealed interface Update : InPaintIntent { - - data class MaskBlur(val value: Int) : Update - - data class OnlyMaskedPadding(val value: Int) : Update - - data class MaskMode(val value: InPaintModel.MaskMode) : Update - - data class MaskContent(val value: InPaintModel.MaskContent) : Update - - data class Area(val value: InPaintModel.Area) : Update - } - - sealed interface ScreenModal : InPaintIntent { - - data class Show(val modal: Modal) : ScreenModal - - data object Dismiss : ScreenModal - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintState.kt deleted file mode 100644 index 211609497..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintState.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint - -import android.graphics.Bitmap -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.presentation.model.InPaintModel -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.android.core.mvi.MviState -import com.shifthackz.aisdv1.core.localization.R as LocalizationR -import com.shifthackz.aisdv1.presentation.R as PresentationR - -@Immutable -data class InPaintState( - val screenModal: Modal = Modal.None, - val bitmap: Bitmap? = null, - val selectedTab: Tab = Tab.IMAGE, - val size: Int = 16, - val model: InPaintModel = InPaintModel(), -) : MviState { - - enum class Tab( - @StringRes val label: Int, - @DrawableRes val iconRes: Int, - ) { - IMAGE( - LocalizationR.string.in_paint_tab_1, - PresentationR.drawable.ic_image, - ), - FORM( - LocalizationR.string.in_paint_tab_2, - PresentationR.drawable.ic_image, - ); - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/components/InPaintComponent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/components/InPaintComponent.kt deleted file mode 100644 index 98c45cbd1..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/components/InPaintComponent.kt +++ /dev/null @@ -1,188 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint.components - -import android.graphics.Bitmap -import android.graphics.Picture -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.StrokeJoin -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.graphics.drawscope.draw -import androidx.compose.ui.graphics.drawscope.drawIntoCanvas -import androidx.compose.ui.graphics.nativeCanvas -import androidx.compose.ui.input.pointer.PointerInputChange -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.model.InPaintModel -import com.shifthackz.aisdv1.presentation.model.MotionEvent -import com.smarttoolfactory.gesture.pointerMotionEvents -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -@Composable -fun InPaintComponent( - modifier: Modifier = Modifier, - drawMode: Boolean = false, - inPaint: InPaintModel = InPaintModel(), - bitmap: Bitmap? = null, - capWidth: Int = 16, - onPathDrawn: (Path) -> Unit = {}, - onPathBitmapDrawn: (Bitmap?) -> Unit = {}, -) { - val scope = rememberCoroutineScope() - Box( - modifier = modifier - .fillMaxWidth() - .aspectRatio(1f), - ) { - bitmap?.takeIf { it.width != 0 && it.height != 0 }?.asImageBitmap()?.let { - Image( - modifier = Modifier.fillMaxSize(), - bitmap = it, - contentDescription = null, - contentScale = ContentScale.FillBounds, - ) - } - inPaint.bitmap?.takeIf { it.width != 0 && it.height != 0 }?.asImageBitmap()?.let { - Image( - modifier = Modifier - .fillMaxSize(), - bitmap = it, - contentDescription = null, - contentScale = ContentScale.FillBounds, - ) - } - var motionEvent by remember { mutableStateOf(MotionEvent.Idle) } - var currentPosition by remember { mutableStateOf(Offset.Unspecified) } - var previousPosition by remember { mutableStateOf(Offset.Unspecified) } - var currentPath by remember { mutableStateOf(Path()) } - val picture = remember { Picture() } - LaunchedEffect(inPaint.paths.size) { - if (picture.width != 0 && picture.height != 0) { - val pathBmp = createBitmapFromPicture(picture) - val resized = bitmap?.let { - Bitmap.createScaledBitmap(pathBmp, it.width, it.height, false) - } - onPathBitmapDrawn(resized ?: pathBmp) - } else { - onPathBitmapDrawn(null) - } - } - Canvas( - modifier = Modifier - .alpha(0.7f) - .fillMaxSize() - .drawWithCache { - val width = this.size.width.toInt() - val height = this.size.height.toInt() - onDrawWithContent { - val pictureCanvas = androidx.compose.ui.graphics.Canvas( - picture.beginRecording(width, height) - ) - draw(this, this.layoutDirection, pictureCanvas, this.size) { - this@onDrawWithContent.drawContent() - } - picture.endRecording() - drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } - } - } - .let { - if (drawMode) it.pointerMotionEvents( - onDown = { pointerInputChange: PointerInputChange -> - currentPosition = pointerInputChange.position - motionEvent = MotionEvent.Down - pointerInputChange.consume() - }, - onMove = { pointerInputChange: PointerInputChange -> - currentPosition = pointerInputChange.position - motionEvent = MotionEvent.Move - pointerInputChange.consume() - }, - onUp = { pointerInputChange: PointerInputChange -> - motionEvent = MotionEvent.Up - pointerInputChange.consume() - }, - delayAfterDownInMillis = 25L, - ) - else it - }, - ) { - if (drawMode) { - when (motionEvent) { - MotionEvent.Down -> { - currentPath.moveTo(currentPosition.x, currentPosition.y) - previousPosition = currentPosition - } - - MotionEvent.Move -> { - currentPath.quadraticTo( - previousPosition.x, - previousPosition.y, - (previousPosition.x + currentPosition.x) / 2, - (previousPosition.y + currentPosition.y) / 2, - ) - previousPosition = currentPosition - } - - MotionEvent.Up -> { - currentPath.lineTo(currentPosition.x, currentPosition.y) - currentPosition = Offset.Unspecified - previousPosition = currentPosition - motionEvent = MotionEvent.Idle - scope.launch { - onPathDrawn(currentPath) - delay(100L) - currentPath = Path() - } - } - - else -> Unit - } - } - - val draw: (Path, Int) -> Unit = { path, cap -> - drawPath( - color = Color.White, - path = path, - style = Stroke( - width = cap.dp.toPx(), - cap = StrokeCap.Round, - join = StrokeJoin.Round, - ) - ) - } - - inPaint.paths.forEach { (p, c) -> draw(p, c) } - draw(currentPath, capWidth) - } - } -} - -private fun createBitmapFromPicture(picture: Picture): Bitmap { - val bitmap = Bitmap.createBitmap( - picture.width, - picture.height, - Bitmap.Config.ARGB_8888 - ) - val canvas = android.graphics.Canvas(bitmap) - canvas.drawPicture(picture) - return bitmap -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/forms/ImageDrawForm.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/forms/ImageDrawForm.kt deleted file mode 100644 index ee730e5af..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/forms/ImageDrawForm.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint.forms - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintIntent -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintState -import com.shifthackz.aisdv1.presentation.screen.inpaint.components.InPaintComponent - -@Composable -fun ImageDrawForm( - modifier: Modifier = Modifier, - state: InPaintState = InPaintState(), - processIntent: (InPaintIntent) -> Unit = {}, -) { - Column( - modifier = modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - InPaintComponent( - drawMode = true, - inPaint = state.model, - bitmap = state.bitmap, - capWidth = state.size, - onPathDrawn = { processIntent(InPaintIntent.DrawPath(it)) }, - onPathBitmapDrawn = { processIntent(InPaintIntent.DrawPathBmp(it)) }, - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderState.kt deleted file mode 100644 index f78977425..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.loader - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.android.core.mvi.MviState - -interface ConfigurationLoaderState : MviState { - - @Immutable - data class StatusNotification(val statusNotification: UiText) : ConfigurationLoaderState -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderViewModel.kt deleted file mode 100755 index e38335340..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderViewModel.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.loader - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.usecase.caching.DataPreLoaderUseCase -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.android.core.mvi.EmptyEffect -import com.shifthackz.android.core.mvi.EmptyIntent -import io.reactivex.rxjava3.kotlin.subscribeBy -import java.util.concurrent.TimeUnit -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -class ConfigurationLoaderViewModel( - dataPreLoaderUseCase: DataPreLoaderUseCase, - dispatchersProvider: DispatchersProvider, - schedulersProvider: SchedulersProvider, - mainRouter: MainRouter, -) : MviRxViewModel() { - - override val initialState = ConfigurationLoaderState.StatusNotification( - LocalizationR.string.splash_status_initializing.asUiText() - ) - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !dataPreLoaderUseCase() - .timeout(15L, TimeUnit.SECONDS) - .doOnSubscribe { - updateState { - ConfigurationLoaderState.StatusNotification( - LocalizationR.string.splash_status_fetching.asUiText() - ) - } - } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = { t -> - updateState { - ConfigurationLoaderState.StatusNotification("Failed loading data".asUiText()) - } - mainRouter.navigateToHomeScreen() - errorLog(t) - }, - onComplete = { - updateState { - ConfigurationLoaderState.StatusNotification( - LocalizationR.string.splash_status_launching.asUiText() - ) - } - mainRouter.navigateToHomeScreen() - }, - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt deleted file mode 100644 index a1fc441c1..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.logger - -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface LoggerIntent : MviIntent { - - data object ReadLogs : LoggerIntent - - data object NavigateBack : LoggerIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt deleted file mode 100644 index 5544f4809..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt +++ /dev/null @@ -1,205 +0,0 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - -package com.shifthackz.aisdv1.presentation.screen.logger - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowBack -import androidx.compose.material.icons.filled.ArrowDownward -import androidx.compose.material.icons.filled.ArrowUpward -import androidx.compose.material.icons.filled.Refresh -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.shifthackz.android.core.mvi.MviComponent -import kotlinx.coroutines.launch -import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun LoggerScreen() { - MviComponent( - viewModel = koinViewModel(), - ) { state, processIntent -> - LoggerScreenContent( - state = state, - processIntent = processIntent, - ) - } -} - -@Composable -@Preview -private fun LoggerScreenContent( - state: LoggerState = LoggerState(), - processIntent: (LoggerIntent) -> Unit = {}, -) { - val scrollState = rememberScrollState() - val scope = rememberCoroutineScope() - Scaffold( - topBar = { - CenterAlignedTopAppBar( - navigationIcon = { - IconButton( - onClick = { processIntent(LoggerIntent.NavigateBack) }, - content = { - Icon( - Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = "Back button", - ) - }, - ) - }, - title = { - Text( - text = stringResource(id = LocalizationR.string.title_debug_logger), - style = MaterialTheme.typography.headlineMedium, - ) - }, - actions = { - AnimatedVisibility( - visible = !state.loading, - enter = fadeIn(), - exit = fadeOut(), - ) { - IconButton( - onClick = { - processIntent(LoggerIntent.ReadLogs) - }, - content = { - Icon( - Icons.Default.Refresh, - contentDescription = "Refresh", - ) - }, - ) - } - } - ) - }, - bottomBar = { - AnimatedVisibility( - visible = !state.loading && state.text.isNotBlank(), - enter = fadeIn(), - exit = fadeOut(), - ) { - Row( - modifier = Modifier - .navigationBarsPadding() - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), - ) { - IconButton( - onClick = { - scope.launch { - scrollState.animateScrollTo(0) - } - }, - content = { - Icon( - Icons.Default.ArrowUpward, - contentDescription = "Down", - ) - }, - ) - IconButton( - onClick = { - scope.launch { - scrollState.animateScrollTo(scrollState.maxValue) - } - }, - content = { - Icon( - Icons.Default.ArrowDownward, - contentDescription = "Down", - ) - }, - ) - } - } - - } - ) { paddingValues -> - val text = if (!state.loading) state.text else "" - val scrollStateHorizontal = rememberScrollState() - if (!state.loading && state.text.isBlank()) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - contentAlignment = Alignment.Center, - ) { - Text( - text = stringResource(id = LocalizationR.string.debug_logger_empty), - textAlign = TextAlign.Center, - ) - } - } - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - .verticalScroll(scrollState), - ) { - AnimatedVisibility( - visible = state.loading, - enter = fadeIn(), - exit = fadeOut(), - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator( - modifier = Modifier - .size(60.dp) - .aspectRatio(1f), - ) - } - } - Text( - modifier = Modifier.horizontalScroll(scrollStateHorizontal), - text = text, - fontFamily = FontFamily.Monospace, - fontSize = 11.sp, - lineHeight = 12.sp, - ) - } - LaunchedEffect(state.text) { - if (!state.loading) { - scrollState.scrollTo(scrollState.maxValue) - } - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt deleted file mode 100644 index 400d6a44a..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.logger - -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.FileLoggingTree -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.android.core.mvi.EmptyEffect -import java.io.File - -class LoggerViewModel( - dispatchersProvider: DispatchersProvider, - private val fileProviderDescriptor: FileProviderDescriptor, - private val mainRouter: MainRouter, -) : MviRxViewModel() { - - override val initialState = LoggerState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - readLogs() - } - - override fun processIntent(intent: LoggerIntent) { - when (intent) { - LoggerIntent.ReadLogs -> readLogs() - LoggerIntent.NavigateBack -> mainRouter.navigateBack() - } - } - - private fun readLogs() { - updateState { it.copy(loading = true, text = "") } - try { - val logFile = File( - fileProviderDescriptor.logsCacheDirPath + - "/" + - FileLoggingTree.LOGGER_FILENAME - ) - val content = logFile.readText() - updateState { - it.copy( - loading = false, - text = content, - ) - } - } catch (e: Exception) { - updateState { it.copy(loading = false) } - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingIntent.kt deleted file mode 100644 index f91f592cb..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingIntent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding - -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface OnBoardingIntent : MviIntent { - data object Navigate : OnBoardingIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingPage.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingPage.kt deleted file mode 100644 index 560da13ec..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingPage.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding - - -enum class OnBoardingPage { - Providers, - Form, - LocalDiffusion, - LookAndFeel, -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingState.kt deleted file mode 100644 index cd5f6872d..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding - -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.android.core.mvi.MviState - -data class OnBoardingState( - val darkThemeToken: DarkThemeToken = DarkThemeToken.FRAPPE, - val appVersion: String = "", -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingViewModel.kt deleted file mode 100644 index 309a018d4..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingViewModel.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.splash.SplashNavigationUseCase -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.postSplashNavigation -import com.shifthackz.android.core.mvi.EmptyEffect -import io.reactivex.rxjava3.kotlin.subscribeBy - -class OnBoardingViewModel( - val launchSource: LaunchSource, - dispatchersProvider: DispatchersProvider, - private val mainRouter: MainRouter, - private val splashNavigationUseCase: SplashNavigationUseCase, - private val preferenceManager: PreferenceManager, - private val schedulersProvider: SchedulersProvider, - private val buildInfoProvider: BuildInfoProvider, -) : MviRxViewModel() { - - override val initialState = OnBoardingState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - updateState { - val token = DarkThemeToken.parse(preferenceManager.designDarkThemeToken) - val version = buildInfoProvider.toString() - it.copy( - darkThemeToken = token, - appVersion = version - ) - } - } - - override fun processIntent(intent: OnBoardingIntent) { - when (intent) { - OnBoardingIntent.Navigate -> { - preferenceManager.onBoardingComplete = true - when (launchSource) { - LaunchSource.SPLASH -> !splashNavigationUseCase() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { action -> - mainRouter.postSplashNavigation(action) - } - - LaunchSource.SETTINGS -> mainRouter.navigateBack() - } - } - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportState.kt deleted file mode 100644 index 8bc9c1111..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportState.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.report - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.domain.entity.ReportReason -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.android.core.mvi.MviState - -data class ReportState( - val loading: Boolean = true, - val screenModal: Modal = Modal.None, - val imageBitmap: Bitmap? = null, - val imageBase64: String = "", - val text: String = "", - val reason: ReportReason = ReportReason.Other, - val reportSent: Boolean = false, -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupScreenTags.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupScreenTags.kt deleted file mode 100644 index 48fa95ea9..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupScreenTags.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup - -object ServerSetupScreenTags { - const val MAIN_BUTTON = "ServerSetupMainButton" - const val CUSTOM_MODEL_SWITCH = "CustomModelSwitch" -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupState.kt deleted file mode 100644 index ca65ed907..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupState.kt +++ /dev/null @@ -1,229 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.common.links.LinksProvider -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.setup.mappers.withNewState -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.android.core.mvi.MviState -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -@Immutable -data class ServerSetupState( - val showBackNavArrow: Boolean = false, - val onBoardingDemo: Boolean = false, - val step: Step = Step.SOURCE, - val mode: ServerSource = ServerSource.AUTOMATIC1111, - val allowedModes: List = ServerSource.entries, - val screenModal: Modal = Modal.None, - val serverUrl: String = "", - val swarmUiUrl: String = "", - val hordeApiKey: String = "", - val huggingFaceApiKey: String = "", - val openAiApiKey: String = "", - val stabilityAiApiKey: String = "", - val hordeDefaultApiKey: Boolean = false, - val demoMode: Boolean = false, - val authType: AuthType = AuthType.ANONYMOUS, - val login: String = "", - val password: String = "", - val huggingFaceModels: List = emptyList(), - val huggingFaceModel: String = "", - val localOnnxModels: List = emptyList(), - val localOnnxCustomModel: Boolean = false, - val localOnnxCustomModelPath: String = "", - val localMediaPipeModels: List = emptyList(), - val localMediaPipeCustomModel: Boolean = false, - val localMediaPipeCustomModelPath: String = "", - val passwordVisible: Boolean = false, - val serverUrlValidationError: UiText? = null, - val swarmUiUrlValidationError: UiText? = null, - val loginValidationError: UiText? = null, - val passwordValidationError: UiText? = null, - val hordeApiKeyValidationError: UiText? = null, - val huggingFaceApiKeyValidationError: UiText? = null, - val openAiApiKeyValidationError: UiText? = null, - val stabilityAiApiKeyValidationError: UiText? = null, - val localCustomOnnxPathValidationError: UiText? = null, - val localCustomMediaPipePathValidationError: UiText? = null, -) : MviState, KoinComponent { - - val localCustomModel: Boolean - get() = if (mode == ServerSource.LOCAL_MICROSOFT_ONNX) { - localOnnxCustomModel - } else { - localMediaPipeCustomModel - } - - val localCustomModelPath: String - get() = if (mode == ServerSource.LOCAL_MICROSOFT_ONNX) { - localOnnxCustomModelPath - } else { - localMediaPipeCustomModelPath - } - - val localModels: List - get() = if (mode == ServerSource.LOCAL_MICROSOFT_ONNX) { - localOnnxModels - } else { - localMediaPipeModels - } - - val localCustomModelPathValidationError: UiText? - get() = if (mode == ServerSource.LOCAL_MICROSOFT_ONNX) { - localCustomOnnxPathValidationError - } else { - localCustomMediaPipePathValidationError - } - - val demoModeUrl: String - get() { - val linksProvider: LinksProvider by inject() - return linksProvider.demoModeUrl - } - - fun withHordeApiKey(value: String) = this.copy( - hordeApiKey = value, - hordeDefaultApiKey = value == Constants.HORDE_DEFAULT_API_KEY, - ) - - fun withCredentials(value: AuthorizationCredentials) = when (value) { - is AuthorizationCredentials.HttpBasic -> copy( - login = value.login, - password = value.password, - ) - - AuthorizationCredentials.None -> this - } - - fun withLocalCustomModelPath(value: String): ServerSetupState = when (mode) { - ServerSource.LOCAL_MICROSOFT_ONNX -> copy( - localOnnxCustomModelPath = value, - localCustomOnnxPathValidationError = null, - ) - - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> copy( - localMediaPipeCustomModelPath = value, - localCustomMediaPipePathValidationError = null, - ) - - else -> this - } - - fun withUpdatedLocalModel(value: LocalModel): ServerSetupState = when (mode) { - ServerSource.LOCAL_MICROSOFT_ONNX -> copy( - localOnnxModels = localOnnxModels.withNewState(value) - ) - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> copy( - localMediaPipeModels = localMediaPipeModels.withNewState(value) - ) - else -> this - } - - fun withDeletedLocalModel(value: LocalModel): ServerSetupState = when (mode) { - ServerSource.LOCAL_MICROSOFT_ONNX -> copy( - screenModal = Modal.None, - localOnnxModels = localOnnxModels.withNewState( - value.copy( - downloadState = DownloadState.Unknown, - downloaded = false, - ), - ) - ) - - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> copy( - screenModal = Modal.None, - localMediaPipeModels = localMediaPipeModels.withNewState( - value.copy( - downloadState = DownloadState.Unknown, - downloaded = false, - ), - ) - ) - - else -> copy(screenModal = Modal.None) - } - - fun withSelectedLocalModel(value: LocalModel): ServerSetupState = when (mode) { - ServerSource.LOCAL_MICROSOFT_ONNX -> copy( - localOnnxModels = localOnnxModels.withNewState( - value.copy(selected = true), - ), - ) - - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> copy( - localMediaPipeModels = localMediaPipeModels.withNewState( - value.copy(selected = true), - ), - ) - - else -> this - } - - fun withAllowCustomModel(value: Boolean): ServerSetupState { - fun List.updateCustomModelSelection(id: String) = withNewState( - find { m -> m.id == id }?.copy(selected = value) - ) - return when (mode) { - ServerSource.LOCAL_MICROSOFT_ONNX -> this.copy( - localOnnxCustomModel = value, - localOnnxModels = localOnnxModels.updateCustomModelSelection( - id = LocalAiModel.CustomOnnx.id, - ), - ) - - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> this.copy( - localMediaPipeCustomModel = value, - localMediaPipeModels = localMediaPipeModels.updateCustomModelSelection( - id = LocalAiModel.CustomMediaPipe.id, - ), - ) - - else -> this - } - } - - enum class Step { - SOURCE, - CONFIGURE; - } - - enum class AuthType { - ANONYMOUS, - HTTP_BASIC; - } - - data class LocalModel( - val id: String, - val name: String, - val size: String, - val downloaded: Boolean = false, - val downloadState: DownloadState = DownloadState.Unknown, - val selected: Boolean = false, - ) -} - -val Configuration.authType: ServerSetupState.AuthType - get() { - val noCredentials = ServerSetupState.AuthType.ANONYMOUS - if (this.demoMode) return noCredentials - if (this.source != ServerSource.AUTOMATIC1111) return noCredentials - return when (this.authCredentials.key) { - AuthorizationCredentials.Key.NONE -> noCredentials - AuthorizationCredentials.Key.HTTP_BASIC -> ServerSetupState.AuthType.HTTP_BASIC - } - } - -fun ServerSetupState.credentialsDomain(): AuthorizationCredentials { - return when (this.authType) { - ServerSetupState.AuthType.ANONYMOUS -> AuthorizationCredentials.None - ServerSetupState.AuthType.HTTP_BASIC -> AuthorizationCredentials.HttpBasic(login, password) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt deleted file mode 100644 index 0e5601955..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt +++ /dev/null @@ -1,503 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.model.Quadruple -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.validation.common.CommonStringValidator -import com.shifthackz.aisdv1.core.validation.path.FilePathValidator -import com.shifthackz.aisdv1.core.validation.url.UrlValidator -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.auth.AuthorizationCredentials -import com.shifthackz.aisdv1.domain.interactor.settings.SetupConnectionInterActor -import com.shifthackz.aisdv1.domain.interactor.wakelock.WakeLockInterActor -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.downloadable.DeleteModelUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.DownloadModelUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.GetConfigurationUseCase -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.screen.setup.mappers.allowedModes -import com.shifthackz.aisdv1.presentation.screen.setup.mappers.mapLocalCustomMediaPipeSwitchState -import com.shifthackz.aisdv1.presentation.screen.setup.mappers.mapLocalCustomOnnxSwitchState -import com.shifthackz.aisdv1.presentation.screen.setup.mappers.mapToUi -import com.shifthackz.aisdv1.presentation.utils.Constants -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.kotlin.subscribeBy - -class ServerSetupViewModel( - launchSource: LaunchSource, - dispatchersProvider: DispatchersProvider, - getConfigurationUseCase: GetConfigurationUseCase, - getLocalOnnxModelsUseCase: GetLocalOnnxModelsUseCase, - getLocalMediaPipeModelsUseCase: GetLocalMediaPipeModelsUseCase, - fetchAndGetHuggingFaceModelsUseCase: FetchAndGetHuggingFaceModelsUseCase, - private val urlValidator: UrlValidator, - private val stringValidator: CommonStringValidator, - private val filePathValidator: FilePathValidator, - private val setupConnectionInterActor: SetupConnectionInterActor, - private val downloadModelUseCase: DownloadModelUseCase, - private val deleteModelUseCase: DeleteModelUseCase, - private val schedulersProvider: SchedulersProvider, - private val preferenceManager: PreferenceManager, - private val wakeLockInterActor: WakeLockInterActor, - private val mainRouter: MainRouter, - private val buildInfoProvider: BuildInfoProvider, -) : MviRxViewModel() { - - override val initialState = ServerSetupState( - showBackNavArrow = launchSource == LaunchSource.SETTINGS, - ) - - override val effectDispatcher = dispatchersProvider.immediate - - private val credentials: AuthorizationCredentials - get() = when (currentState.mode) { - ServerSource.AUTOMATIC1111 -> { - if (!currentState.demoMode) currentState.credentialsDomain() - else AuthorizationCredentials.None - } - - ServerSource.SWARM_UI -> currentState.credentialsDomain() - - else -> AuthorizationCredentials.None - } - - private val downloadDisposables: MutableList> = mutableListOf() - - init { - !Single.zip( - getConfigurationUseCase(), - getLocalOnnxModelsUseCase(), - getLocalMediaPipeModelsUseCase(), - fetchAndGetHuggingFaceModelsUseCase(), - ::Quadruple, - ) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { (configuration, onnxModels, mpModels, hfModels) -> - updateState { state -> - state.copy( - huggingFaceModels = hfModels.map(HuggingFaceModel::alias), - huggingFaceModel = configuration.huggingFaceModel, - huggingFaceApiKey = configuration.huggingFaceApiKey, - openAiApiKey = configuration.openAiApiKey, - stabilityAiApiKey = configuration.stabilityAiApiKey, - localOnnxModels = onnxModels.mapToUi(), - localOnnxCustomModel = onnxModels.mapLocalCustomOnnxSwitchState(), - localOnnxCustomModelPath = configuration.localOnnxModelPath, - localMediaPipeModels = mpModels.mapToUi(), - localMediaPipeCustomModel = mpModels.mapLocalCustomMediaPipeSwitchState(), - localMediaPipeCustomModelPath = configuration.localMediaPipeModelPath, - mode = configuration.source, - allowedModes = buildInfoProvider.allowedModes, - demoMode = configuration.demoMode, - serverUrl = configuration.serverUrl, - swarmUiUrl = configuration.swarmUiUrl, - authType = configuration.authType, - ) - .withCredentials(configuration.authCredentials) - .withHordeApiKey(configuration.hordeApiKey) - } - } - } - - override fun onCleared() { - downloadDisposables.forEach { (_, disposable) -> - disposable.dispose() - } - super.onCleared() - } - - override fun processIntent(intent: ServerSetupIntent) = when (intent) { - is ServerSetupIntent.AllowLocalCustomModel -> updateState { state -> - state.withAllowCustomModel(intent.allow) - } - - ServerSetupIntent.DismissDialog -> setScreenModal(Modal.None) - - is ServerSetupIntent.LocalModel.ClickReduce -> localModelDownloadClickReducer(intent.model) - - is ServerSetupIntent.LocalModel.DeleteConfirm -> updateState { - !deleteModelUseCase(intent.model.id) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) - it.withDeletedLocalModel(intent.model) - } - - is ServerSetupIntent.SelectLocalModel -> updateState { state -> - state.withSelectedLocalModel(intent.model) - } - - ServerSetupIntent.MainButtonClick -> when (currentState.step) { - ServerSetupState.Step.SOURCE -> updateState { - it.copy(step = ServerSetupState.Step.CONFIGURE) - } - - ServerSetupState.Step.CONFIGURE -> validateAndConnectToServer() - } - - is ServerSetupIntent.UpdateAuthType -> updateState { - it.copy(authType = intent.type) - } - - is ServerSetupIntent.UpdateDemoMode -> updateState { - it.copy(demoMode = intent.value) - } - - is ServerSetupIntent.UpdateHordeApiKey -> updateState { - it.copy(hordeApiKey = intent.key, hordeApiKeyValidationError = null) - } - - is ServerSetupIntent.UpdateHordeDefaultApiKey -> updateState { - it.copy(hordeDefaultApiKey = intent.value) - } - - is ServerSetupIntent.UpdateHuggingFaceApiKey -> updateState { - it.copy(huggingFaceApiKey = intent.key) - } - - is ServerSetupIntent.UpdateHuggingFaceModel -> updateState { - it.copy(huggingFaceModel = intent.model) - } - - is ServerSetupIntent.UpdateLogin -> updateState { - it.copy(login = intent.login, loginValidationError = null) - } - - is ServerSetupIntent.UpdateOpenAiApiKey -> updateState { - it.copy(openAiApiKey = intent.key) - } - - is ServerSetupIntent.UpdatePassword -> updateState { - it.copy(password = intent.password, passwordValidationError = null) - } - - is ServerSetupIntent.UpdatePasswordVisibility -> updateState { - it.copy(passwordVisible = !intent.visible) - } - - is ServerSetupIntent.UpdateServerMode -> updateState { - it.copy(mode = intent.mode) - } - - is ServerSetupIntent.UpdateServerUrl -> updateState { - it.copy(serverUrl = intent.url, serverUrlValidationError = null) - } - - is ServerSetupIntent.UpdateSwarmUiUrl -> updateState { - it.copy(swarmUiUrl = intent.url, swarmUiUrlValidationError = null) - } - - is ServerSetupIntent.LaunchUrl -> { - emitEffect(ServerSetupEffect.LaunchUrl(intent.url)) - } - - ServerSetupIntent.LaunchManageStoragePermission -> { - emitEffect(ServerSetupEffect.LaunchManageStoragePermission) - } - - ServerSetupIntent.NavigateBack -> if (currentState.step == ServerSetupState.Step.entries.first()) { - mainRouter.navigateBack() - } else { - emitEffect(ServerSetupEffect.HideKeyboard) - updateState { - it.copy(step = ServerSetupState.Step.entries[it.step.ordinal - 1]) - } - } - - is ServerSetupIntent.UpdateStabilityAiApiKey -> updateState { - it.copy(stabilityAiApiKey = intent.key) - } - - ServerSetupIntent.ConnectToLocalHost -> connectToServer() - - is ServerSetupIntent.SelectLocalModelPath -> updateState { state -> - state.withLocalCustomModelPath(intent.value) - } - - is ServerSetupIntent.LocalModel.DownloadConfirm -> with(intent) { - download(modelId, url) - } - } - - private fun validateAndConnectToServer() { - if (!validate()) return - connectToServer() - } - - private fun connectToServer() { - emitEffect(ServerSetupEffect.HideKeyboard) - !when (currentState.mode) { - ServerSource.HORDE -> connectToHorde() - ServerSource.LOCAL_MICROSOFT_ONNX -> connectToLocalDiffusion() - ServerSource.AUTOMATIC1111 -> connectToAutomaticInstance() - ServerSource.HUGGING_FACE -> connectToHuggingFace() - ServerSource.OPEN_AI -> connectToOpenAi() - ServerSource.STABILITY_AI -> connectToStabilityAi() - ServerSource.SWARM_UI -> connectToSwarmUi() - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> connectToMediaPipe() - } - .doOnSubscribe { setScreenModal(Modal.Communicating(canCancel = false)) } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { result -> - result.fold( - onSuccess = { onSetupComplete() }, - onFailure = { t -> - val message = t.localizedMessage ?: "Bad key" - setScreenModal(Modal.Error(message.asUiText())) - }, - ) - } - } - - private fun validate(): Boolean = when (currentState.mode) { - ServerSource.AUTOMATIC1111 -> { - if (currentState.demoMode) true - else validateServerUrlAndCredentials(currentState.serverUrl) - } - - ServerSource.SWARM_UI -> validateServerUrlAndCredentials(currentState.swarmUiUrl) - - ServerSource.HORDE -> { - if (currentState.hordeDefaultApiKey) true - else { - val validation = stringValidator(currentState.hordeApiKey) - updateState { - it.copy(hordeApiKeyValidationError = validation.mapToUi()) - } - validation.isValid - } - } - - ServerSource.LOCAL_MICROSOFT_ONNX -> if (currentState.localOnnxCustomModel) { - val validation = filePathValidator(currentState.localOnnxCustomModelPath) - updateState { - it.copy(localCustomOnnxPathValidationError = validation.mapToUi()) - } - validation.isValid - } else { - currentState.localOnnxModels.find { it.selected && it.downloaded } != null - } - - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> when { - buildInfoProvider.type == BuildType.FOSS -> false - currentState.localMediaPipeCustomModel -> { - val validation = filePathValidator(currentState.localMediaPipeCustomModelPath) - updateState { - it.copy(localCustomMediaPipePathValidationError = validation.mapToUi()) - } - validation.isValid - } - - else -> { - currentState.localMediaPipeModels.find { it.selected && it.downloaded } != null - } - } - - ServerSource.HUGGING_FACE -> { - val validation = stringValidator(currentState.huggingFaceApiKey) - updateState { - it.copy(huggingFaceApiKeyValidationError = validation.mapToUi()) - } - validation.isValid - } - - ServerSource.OPEN_AI -> { - val validation = stringValidator(currentState.openAiApiKey) - updateState { - it.copy(openAiApiKeyValidationError = validation.mapToUi()) - } - validation.isValid - } - - ServerSource.STABILITY_AI -> { - val validation = stringValidator(currentState.stabilityAiApiKey) - updateState { - it.copy(stabilityAiApiKeyValidationError = validation.mapToUi()) - } - validation.isValid - } - } - - private fun validateServerUrlAndCredentials(url: String): Boolean { - val serverUrlValidation = urlValidator(url) - var isValid = serverUrlValidation.isValid - updateState { state -> - var newState = state.copy( - serverUrlValidationError = if (state.mode == ServerSource.AUTOMATIC1111) { - serverUrlValidation.mapToUi() - } else { - state.serverUrlValidationError - }, - swarmUiUrlValidationError = if (state.mode == ServerSource.SWARM_UI) { - serverUrlValidation.mapToUi() - } else { - state.swarmUiUrlValidationError - }, - ) - if (currentState.authType == ServerSetupState.AuthType.HTTP_BASIC) { - val loginValidation = stringValidator(currentState.login) - val passwordValidation = stringValidator(currentState.password) - newState = newState.copy( - loginValidationError = loginValidation.mapToUi(), - passwordValidationError = passwordValidation.mapToUi() - ) - isValid = isValid && loginValidation.isValid && passwordValidation.isValid - } - if (serverUrlValidation.validationError is UrlValidator.Error.Localhost - && newState.loginValidationError == null - && newState.passwordValidationError == null - ) { - newState = newState.copy(screenModal = Modal.ConnectLocalHost) - } - newState - } - return isValid - } - - private fun connectToAutomaticInstance(): Single> { - val demoMode = currentState.demoMode - val connectUrl = if (demoMode) currentState.demoModeUrl else currentState.serverUrl - return setupConnectionInterActor.connectToA1111( - url = connectUrl, - isDemo = demoMode, - credentials = credentials, - ) - } - - private fun connectToSwarmUi() = setupConnectionInterActor.connectToSwarmUi( - url = currentState.swarmUiUrl, - credentials = credentials, - ) - - private fun connectToHuggingFace() = with(currentState) { - setupConnectionInterActor.connectToHuggingFace( - apiKey = huggingFaceApiKey, - model = huggingFaceModel, - ) - } - - private fun connectToOpenAi() = setupConnectionInterActor.connectToOpenAi( - apiKey = currentState.openAiApiKey, - ) - - private fun connectToStabilityAi() = setupConnectionInterActor.connectToStabilityAi( - apiKey = currentState.stabilityAiApiKey, - ) - - private fun connectToHorde(): Single> { - val testApiKey = if (currentState.hordeDefaultApiKey) { - Constants.HORDE_DEFAULT_API_KEY - } else { - currentState.hordeApiKey - } - return setupConnectionInterActor.connectToHorde(testApiKey) - } - - private fun connectToLocalDiffusion(): Single> { - preferenceManager.localOnnxCustomModelPath = currentState.localOnnxCustomModelPath - val localModelId = currentState.localOnnxModels.find { it.selected }?.id ?: "" - return setupConnectionInterActor.connectToLocal(localModelId) - } - - private fun connectToMediaPipe(): Single> { - preferenceManager.localMediaPipeCustomModelPath = currentState.localMediaPipeCustomModelPath - val localModelId = currentState.localMediaPipeModels.find { it.selected }?.id ?: "" - return setupConnectionInterActor.connectToMediaPipe(localModelId) - } - - private fun localModelDownloadClickReducer(value: ServerSetupState.LocalModel) { - fun localModel(): ServerSetupState.LocalModel = - currentState.localModels.firstOrNull { it.id == value.id } - ?.let { value.copy(selected = it.selected) } - ?: value - - when { - // User cancels download - localModel().downloadState is DownloadState.Downloading -> { - val index = downloadDisposables.indexOfFirst { it.first == localModel().id } - if (index != -1) { - downloadDisposables[index].second.dispose() - downloadDisposables.removeAt(index) - } - !deleteModelUseCase(localModel().id) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) - updateState { state -> - state.withUpdatedLocalModel( - value = localModel().copy(downloadState = DownloadState.Unknown), - ) - } - } - // User deletes local model - localModel().downloaded -> updateState { - it.copy(screenModal = Modal.DeleteLocalModelConfirm(localModel())) - } - // User requested new download operation - else -> setScreenModal(Modal.SelectDownloadSource(localModel().id)) - } - } - - private fun download(modelId: String, url: String) { - val localModel = - currentState.localModels.firstOrNull { it.id == modelId } ?: return - - updateState { state -> - state.withUpdatedLocalModel( - localModel.copy(downloadState = DownloadState.Downloading()), - ) - } - !downloadModelUseCase(localModel.id, url) - .distinctUntilChanged() - .doOnSubscribe { wakeLockInterActor.acquireWakelockUseCase() } - .doFinally { wakeLockInterActor.releaseWakeLockUseCase() } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = { t -> - errorLog(t) - val message = t.localizedMessage ?: "Error" - updateState { state -> - state.withUpdatedLocalModel( - localModel.copy( - downloadState = DownloadState.Error(t), - ), - ) - } - setScreenModal(Modal.Error(message.asUiText())) - }, - onNext = { downloadState -> - updateState { state -> - state.withUpdatedLocalModel( - localModel.copy( - downloadState = downloadState, - downloaded = downloadState is DownloadState.Complete - ), - ) - } - }, - ) - .also { downloadDisposables.add(localModel.id to it) } - } - - private fun setScreenModal(value: Modal) = updateState { - it.copy(screenModal = value) - } - - private fun onSetupComplete() { - preferenceManager.forceSetupAfterUpdate = false - processIntent(ServerSetupIntent.DismissDialog) - mainRouter.navigateToHomeScreen() - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/LocalDiffusionForm.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/LocalDiffusionForm.kt deleted file mode 100644 index dac9efb94..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/LocalDiffusionForm.kt +++ /dev/null @@ -1,430 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.forms - -import android.content.Intent -import android.provider.DocumentsContract -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Refresh -import androidx.compose.material.icons.outlined.FileDownload -import androidx.compose.material.icons.outlined.FileDownloadDone -import androidx.compose.material.icons.outlined.FileDownloadOff -import androidx.compose.material.icons.outlined.Landslide -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.times -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.file.LOCAL_DIFFUSION_CUSTOM_PATH -import com.shifthackz.aisdv1.core.extensions.getRealPath -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupScreenTags.CUSTOM_MODEL_SWITCH -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun LocalDiffusionForm( - modifier: Modifier = Modifier, - state: ServerSetupState, - buildInfoProvider: BuildInfoProvider = BuildInfoProvider.stub, - processIntent: (ServerSetupIntent) -> Unit = {}, -) { - val modelItemUi: @Composable (ServerSetupState.LocalModel) -> Unit = { model -> - Column( - modifier = Modifier - .padding(vertical = 8.dp) - .fillMaxWidth() - .clip(RoundedCornerShape(16.dp)) - .background(color = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.8f)) - .defaultMinSize(minHeight = 50.dp) - .border( - width = 2.dp, - shape = RoundedCornerShape(16.dp), - color = if (model.selected) MaterialTheme.colorScheme.primary else Color.Transparent, - ) - .clickable { processIntent(ServerSetupIntent.SelectLocalModel(model)) }, - ) { - Row( - modifier = Modifier - .padding(vertical = 4.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically, - ) { - val icon = when (model.downloadState) { - is DownloadState.Downloading -> Icons.Outlined.FileDownload - else -> when { - model.id == LocalAiModel.CustomOnnx.id -> Icons.Outlined.Landslide - model.id == LocalAiModel.CustomMediaPipe.id -> Icons.Outlined.Landslide - model.downloaded -> Icons.Outlined.FileDownloadDone - else -> Icons.Outlined.FileDownloadOff - } - } - Icon( - modifier = modifier - .padding(start = 8.dp) - .size(48.dp), - imageVector = icon, - contentDescription = "Download state", - ) - Column( - modifier = Modifier - .padding(horizontal = 4.dp) - .weight(1f) - ) { - Text( - text = model.name, - overflow = TextOverflow.Ellipsis, - maxLines = 2 - ) - when (model.id) { - LocalAiModel.CustomOnnx.id, - LocalAiModel.CustomMediaPipe.id -> Unit - - else -> Text( - text = model.size, - maxLines = 1 - ) - } - } - // Do not display action button for custom model - if (model.id != LocalAiModel.CustomOnnx.id - && model.id != LocalAiModel.CustomMediaPipe.id - ) { - Button( - modifier = Modifier.padding(end = 8.dp), - onClick = { processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) }, - ) { - Text( - text = stringResource( - id = when (model.downloadState) { - is DownloadState.Downloading -> LocalizationR.string.cancel - is DownloadState.Error -> LocalizationR.string.retry - else -> { - if (model.downloaded) LocalizationR.string.delete - else LocalizationR.string.download - } - } - ), - color = LocalContentColor.current, - maxLines = 1 - ) - } - } - } - if (model.id == LocalAiModel.CustomOnnx.id - || model.id == LocalAiModel.CustomMediaPipe.id - ) { - Column( - modifier = Modifier.padding(8.dp), - ) { - Text( - text = stringResource(id = LocalizationR.string.model_local_custom_title), - style = MaterialTheme.typography.bodyMedium, - ) - if (model.id == LocalAiModel.CustomOnnx.id) { - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(id = LocalizationR.string.model_local_custom_sub_title), - style = MaterialTheme.typography.bodyMedium, - ) - Spacer(modifier = Modifier.height(4.dp)) - - fun folderModifier(treeNum: Int) = - Modifier.padding(start = (treeNum - 1) * 12.dp) - - val folderStyle = MaterialTheme.typography.bodySmall - Text( - modifier = Modifier.padding(start = 12.dp), - text = state.localOnnxCustomModelPath, - style = folderStyle, - ) - - Text( - modifier = folderModifier(3), - text = "text_encoder", - style = folderStyle, - ) - Text( - modifier = folderModifier(4), - text = "model.ort", - style = folderStyle, - ) - - Text( - modifier = folderModifier(3), - text = "tokenizer", - style = folderStyle, - ) - Text( - modifier = folderModifier(4), - text = "merges.txt", - style = folderStyle, - ) - Text( - modifier = folderModifier(3), - text = "special_tokens_map.json", - style = folderStyle, - ) - Text( - modifier = folderModifier(4), - text = "tokenizer_config.json", - style = folderStyle, - ) - Text( - modifier = folderModifier(4), - text = "vocab.json", - style = folderStyle, - ) - - Text( - modifier = folderModifier(3), - text = "unet", - style = folderStyle, - ) - Text( - modifier = folderModifier(4), - text = "model.ort", - style = folderStyle, - ) - - Text( - modifier = folderModifier(3), - text = "vae_decoder", - style = folderStyle, - ) - Text( - modifier = folderModifier(4), - text = "model.ort", - style = folderStyle, - ) - } - } - } - when (model.downloadState) { - is DownloadState.Downloading -> { - LinearProgressIndicator( - progress = { model.downloadState.percent / 100f }, - modifier = Modifier - .padding(8.dp) - .fillMaxWidth(), - ) - } - - is DownloadState.Error -> { - Text( - modifier = Modifier - .padding(horizontal = 8.dp) - .padding(bottom = 8.dp), - text = stringResource(id = LocalizationR.string.error_download_fail), - ) - } - - else -> Unit - } - } - } - - Column( - modifier = modifier.padding(horizontal = 16.dp), - ) { - Text( - modifier = Modifier - .fillMaxWidth() - .padding(top = 32.dp, bottom = 8.dp), - text = stringResource( - id = if (state.mode == ServerSource.LOCAL_MICROSOFT_ONNX) { - LocalizationR.string.hint_local_diffusion_title - } else { - LocalizationR.string.hint_mediapipe_title - }, - ), - style = MaterialTheme.typography.bodyLarge, - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - ) - Text( - modifier = Modifier.padding(top = 16.dp, bottom = 16.dp), - text = stringResource( - id = if (state.mode == ServerSource.LOCAL_MICROSOFT_ONNX) { - LocalizationR.string.hint_local_diffusion_sub_title - } else { - LocalizationR.string.hint_mediapipe_sub_title - }, - ), - style = MaterialTheme.typography.bodyMedium, - ) - if (buildInfoProvider.type != BuildType.PLAY) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Switch( - modifier = Modifier.testTag(CUSTOM_MODEL_SWITCH), - checked = state.localCustomModel, - onCheckedChange = { - processIntent(ServerSetupIntent.AllowLocalCustomModel(it)) - }, - ) - Text( - modifier = Modifier.padding(start = 8.dp), - text = stringResource(id = LocalizationR.string.model_local_custom_switch), - ) - } - } - if (state.localCustomModel && buildInfoProvider.type != BuildType.PLAY) { - Text( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(vertical = 8.dp), - text = stringResource(id = LocalizationR.string.model_local_permission_header), - style = MaterialTheme.typography.bodyLarge, - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - ) - Text( - modifier = Modifier.padding(vertical = 8.dp), - text = stringResource(id = LocalizationR.string.model_local_permission_title), - style = MaterialTheme.typography.bodyMedium, - ) - OutlinedButton( - modifier = Modifier - .fillMaxSize() - .padding(vertical = 8.dp), - onClick = { processIntent(ServerSetupIntent.LaunchManageStoragePermission) }, - ) { - Text( - text = stringResource(id = LocalizationR.string.model_local_permission_button), - color = LocalContentColor.current, - ) - } - Text( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(vertical = 8.dp), - text = stringResource(id = LocalizationR.string.model_local_path_header), - style = MaterialTheme.typography.bodyLarge, - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - ) - val context = LocalContext.current - val uriFlags = - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - val launcher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult() - ) { result -> - result.data?.data?.let { uri -> - context.contentResolver.takePersistableUriPermission(uri, uriFlags) - val docUri = DocumentsContract.buildDocumentUriUsingTree( - uri, - DocumentsContract.getTreeDocumentId(uri) - ) - getRealPath(context, docUri) - ?.let(ServerSetupIntent::SelectLocalModelPath) - ?.let(processIntent::invoke) - } - } - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 14.dp), - value = state.localCustomModelPath, - onValueChange = { string -> - string.filter { it != '\n' } - .let(ServerSetupIntent::SelectLocalModelPath) - .let(processIntent::invoke) - }, - enabled = true, - label = { Text(stringResource(LocalizationR.string.model_local_path_title)) }, - trailingIcon = { - IconButton( - onClick = { - processIntent( - ServerSetupIntent.SelectLocalModelPath(LOCAL_DIFFUSION_CUSTOM_PATH) - ) - }, - content = { - Icon( - imageVector = Icons.Default.Refresh, - contentDescription = "Reset", - ) - }, - ) - }, - isError = state.localCustomModelPathValidationError != null, - supportingText = { - state.localCustomModelPathValidationError - ?.let { Text(it.asString(), color = MaterialTheme.colorScheme.error) } - }, - colors = textFieldColors, - ) - OutlinedButton( - modifier = Modifier - .fillMaxSize() - .padding(top = 4.dp, bottom = 8.dp), - onClick = { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - addFlags(uriFlags) - } - launcher.launch(intent) - }, - ) { - Text( - text = stringResource(id = LocalizationR.string.model_local_path_button), - color = LocalContentColor.current, - ) - } - Spacer(modifier = Modifier.height(8.dp)) - } - state.localModels - .filter { - val customPredicate = - it.id == LocalAiModel.CustomOnnx.id || it.id == LocalAiModel.CustomMediaPipe.id - if (state.localCustomModel) customPredicate else !customPredicate - } - .forEach { localModel -> modelItemUi(localModel) } - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(id = LocalizationR.string.hint_local_diffusion_warning), - style = MaterialTheme.typography.bodyMedium, - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/MediaPipeForm.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/MediaPipeForm.kt deleted file mode 100644 index 3bb26b1dd..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/MediaPipeForm.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.forms - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState - -@Composable -fun MediaPipeForm( - modifier: Modifier = Modifier, - state: ServerSetupState, - buildInfoProvider: BuildInfoProvider = BuildInfoProvider.stub, - processIntent: (ServerSetupIntent) -> Unit = {}, -) { - when (buildInfoProvider.type) { - BuildType.FOSS -> { - - } - - else -> LocalDiffusionForm( - modifier = modifier, - state = state, - buildInfoProvider = buildInfoProvider, - processIntent = processIntent, - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/LocalModelMappers.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/LocalModelMappers.kt deleted file mode 100644 index 3cd1e443b..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/LocalModelMappers.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.mappers - -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState - -fun List.mapToUi(): List = map(LocalAiModel::mapToUi) - -fun List.mapLocalCustomOnnxSwitchState(): Boolean = - find { it.selected && it.id == LocalAiModel.CustomOnnx.id } != null - -fun List.mapLocalCustomMediaPipeSwitchState(): Boolean = - find { it.selected && it.id == LocalAiModel.CustomMediaPipe.id } != null - -fun LocalAiModel.mapToUi(): ServerSetupState.LocalModel = with(this) { - ServerSetupState.LocalModel( - id = id, - name = name, - size = size, - downloaded = downloaded, - selected = selected, - ) -} - -fun List.withNewState( - model: ServerSetupState.LocalModel?, -): List = - map { - if (model == null) return@map it - if (it.id == model.id) model - else { - if (model.selected) it.copy(selected = false) - else it - } - } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ModesMapper.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ModesMapper.kt deleted file mode 100644 index 1dd0b9c28..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ModesMapper.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.mappers - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.domain.entity.ServerSource - -val BuildInfoProvider.allowedModes: List - get() = ServerSource - .entries - .filter { it.allowedInBuilds.contains(type) } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationFilePathErrorMapper.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationFilePathErrorMapper.kt deleted file mode 100644 index 08467bf9f..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationFilePathErrorMapper.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.mappers - -import com.shifthackz.aisdv1.core.localization.R -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.validation.ValidationResult -import com.shifthackz.aisdv1.core.validation.path.FilePathValidator - -fun ValidationResult.mapToUi(): UiText? { - if (this.isValid) return null - return when (validationError as FilePathValidator.Error) { - FilePathValidator.Error.Empty -> R.string.error_empty_field - FilePathValidator.Error.Invalid -> R.string.error_invalid - }.asUiText() -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationStringErrorMapper.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationStringErrorMapper.kt deleted file mode 100644 index dad53edb6..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationStringErrorMapper.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.mappers - -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.validation.ValidationResult -import com.shifthackz.aisdv1.core.validation.common.CommonStringValidator -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -fun ValidationResult.mapToUi(): UiText? { - if (this.isValid) return null - return when (validationError as CommonStringValidator.Error) { - CommonStringValidator.Error.Empty -> LocalizationR.string.error_empty_field - }.asUiText() -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationUrlMapper.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationUrlMapper.kt deleted file mode 100644 index 99955e3e1..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationUrlMapper.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.mappers - -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.validation.ValidationResult -import com.shifthackz.aisdv1.core.validation.url.UrlValidator -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -fun ValidationResult.mapToUi(): UiText? { - if (this.isValid) return null - return when (validationError as UrlValidator.Error) { - UrlValidator.Error.BadScheme -> LocalizationR.string.error_invalid_scheme - UrlValidator.Error.BadPort -> LocalizationR.string.error_invalid_port - UrlValidator.Error.Empty -> LocalizationR.string.error_empty_url - UrlValidator.Error.Invalid -> LocalizationR.string.error_invalid_url - UrlValidator.Error.Localhost -> null - }?.asUiText() -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/ConfigurationStep.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/ConfigurationStep.kt deleted file mode 100644 index d25e82371..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/ConfigurationStep.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.steps - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.screen.setup.forms.Automatic1111Form -import com.shifthackz.aisdv1.presentation.screen.setup.forms.HordeForm -import com.shifthackz.aisdv1.presentation.screen.setup.forms.HuggingFaceForm -import com.shifthackz.aisdv1.presentation.screen.setup.forms.LocalDiffusionForm -import com.shifthackz.aisdv1.presentation.screen.setup.forms.MediaPipeForm -import com.shifthackz.aisdv1.presentation.screen.setup.forms.OpenAiForm -import com.shifthackz.aisdv1.presentation.screen.setup.forms.StabilityAiForm -import com.shifthackz.aisdv1.presentation.screen.setup.forms.SwarmUiForm - -@Composable -fun ConfigurationStep( - modifier: Modifier = Modifier, - state: ServerSetupState, - buildInfoProvider: BuildInfoProvider = BuildInfoProvider.stub, - processIntent: (ServerSetupIntent) -> Unit = {}, -) { - BaseServerSetupStateWrapper(modifier) { - when (state.mode) { - ServerSource.AUTOMATIC1111 -> Automatic1111Form( - state = state, - processIntent = processIntent, - ) - - ServerSource.HORDE -> HordeForm( - state = state, - processIntent = processIntent, - ) - - ServerSource.LOCAL_MICROSOFT_ONNX -> LocalDiffusionForm( - state = state, - buildInfoProvider = buildInfoProvider, - processIntent = processIntent, - ) - - ServerSource.HUGGING_FACE -> HuggingFaceForm( - state = state, - processIntent = processIntent, - ) - - ServerSource.OPEN_AI -> OpenAiForm( - state = state, - processIntent = processIntent, - ) - - ServerSource.STABILITY_AI -> StabilityAiForm( - state = state, - processIntent = processIntent, - ) - - ServerSource.SWARM_UI -> SwarmUiForm( - state = state, - processIntent = processIntent, - ) - - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> MediaPipeForm( - state = state, - buildInfoProvider = buildInfoProvider, - processIntent = processIntent, - ) - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashViewModel.kt deleted file mode 100644 index 721ccbb30..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashViewModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.splash - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.usecase.splash.SplashNavigationUseCase -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.postSplashNavigation -import com.shifthackz.android.core.mvi.EmptyEffect -import com.shifthackz.android.core.mvi.EmptyIntent -import com.shifthackz.android.core.mvi.EmptyState -import io.reactivex.rxjava3.kotlin.subscribeBy - -class SplashViewModel( - mainRouter: MainRouter, - splashNavigationUseCase: SplashNavigationUseCase, - dispatchersProvider: DispatchersProvider, - schedulersProvider: SchedulersProvider, -) : MviRxViewModel() { - - override val initialState = EmptyState - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !splashNavigationUseCase() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { action -> mainRouter.postSplashNavigation(action) } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageScreen.kt deleted file mode 100755 index b511674a3..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageScreen.kt +++ /dev/null @@ -1,202 +0,0 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - -package com.shifthackz.aisdv1.presentation.screen.txt2img - -import androidx.compose.foundation.ScrollState -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AutoFixNormal -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.Menu -import androidx.compose.material3.Button -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.widget.input.GenerationInputForm -import com.shifthackz.aisdv1.presentation.widget.toolbar.GenerationBottomToolbar -import com.shifthackz.aisdv1.presentation.widget.work.BackgroundWorkWidget -import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun TextToImageScreen() { - MviComponent( - viewModel = koinViewModel(), - ) { state, intentHandler -> - TextToImageScreenContent( - modifier = Modifier.fillMaxSize(), - state = state, - processIntent = intentHandler, - ) - } -} - -@Composable -fun TextToImageScreenContent( - modifier: Modifier = Modifier, - state: TextToImageState, - scrollState: ScrollState = rememberScrollState(), - processIntent: (GenerationMviIntent) -> Unit = {}, -) { - val promptChipTextFieldState = remember { mutableStateOf(TextFieldValue()) } - val negativePromptChipTextFieldState = remember { mutableStateOf(TextFieldValue()) } - val keyboardController = LocalSoftwareKeyboardController.current - Box(modifier) { - Scaffold( - topBar = { - Column { - CenterAlignedTopAppBar( - navigationIcon = { - IconButton(onClick = { - processIntent(GenerationMviIntent.Drawer(DrawerIntent.Open)) - }) { - Icon( - imageVector = Icons.Default.Menu, - contentDescription = "Menu", - ) - } - }, - title = { - Text( - text = stringResource(id = LocalizationR.string.title_text_to_image), - style = MaterialTheme.typography.headlineMedium, - ) - }, - actions = { - IconButton(onClick = { - processIntent( - GenerationMviIntent.SetModal( - Modal.PromptBottomSheet( - AiGenerationResult.Type.TEXT_TO_IMAGE, - ), - ), - ) - }) { - Icon( - imageVector = Icons.Default.Edit, - contentDescription = null, - ) - } - }, - windowInsets = WindowInsets(0, 0, 0, 0), - ) - BackgroundWorkWidget( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .padding(vertical = 4.dp), - ) - } - }, - content = { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .verticalScroll(scrollState) - .padding(horizontal = 16.dp), - ) { - GenerationInputForm( - state = state, - promptChipTextFieldState = promptChipTextFieldState, - negativePromptChipTextFieldState = negativePromptChipTextFieldState, - processIntent = processIntent, - ) - } - }, - bottomBar = { - GenerationBottomToolbar( - strokeAccentState = !state.hasValidationErrors, - state = state, - processIntent = processIntent, - ) { - Button( - modifier = Modifier - .height(height = 60.dp) - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = 16.dp), - onClick = { - keyboardController?.hide() - promptChipTextFieldState.value.text.takeIf(String::isNotBlank) - ?.let { "${state.prompt}, ${it.trim()}" } - ?.let(GenerationMviIntent.Update::Prompt) - ?.let(processIntent::invoke) - ?.also { promptChipTextFieldState.value = TextFieldValue("") } - - negativePromptChipTextFieldState.value.text.takeIf(String::isNotBlank) - ?.let { "${state.negativePrompt}, ${it.trim()}" } - ?.let(GenerationMviIntent.Update::NegativePrompt) - ?.let(processIntent::invoke) - ?.also { - negativePromptChipTextFieldState.value = TextFieldValue("") - } - - processIntent(GenerationMviIntent.Generate) - }, - enabled = !state.hasValidationErrors - ) { - Icon( - modifier = Modifier.size(18.dp), - imageVector = Icons.Default.AutoFixNormal, - contentDescription = "Imagine", - ) - Text( - modifier = Modifier.padding(start = 8.dp), - text = stringResource(id = LocalizationR.string.action_generate), - color = LocalContentColor.current, - ) - } - } - } - ) - ModalRenderer( - screenModal = state.screenModal, - processIntent = { (it as? GenerationMviIntent)?.let(processIntent::invoke) }, - ) - } -} - -//region PREVIEWS -@Composable -@Preview(showSystemUi = true, showBackground = true) -fun PreviewStateContent() { - TextToImageScreenContent( - modifier = Modifier.fillMaxSize(), - state = TextToImageState( - prompt = "Opel Astra H OPC", - negativePrompt = "White background", - samplingSteps = 55, - availableSamplers = listOf("Euler a") - ) - ) -} -//endregion diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageState.kt deleted file mode 100644 index 04466de67..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageState.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.txt2img - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.validation.ValidationResult -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.OpenAiQuality -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.OpenAiStyle -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.StabilityAiClipGuidance -import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset -import com.shifthackz.aisdv1.domain.entity.TextToImagePayload -import com.shifthackz.aisdv1.presentation.core.GenerationMviState -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Immutable -data class TextToImageState( - override val onBoardingDemo: Boolean = false, - override val screenModal: Modal = Modal.None, - override val mode: ServerSource = ServerSource.AUTOMATIC1111, - override val advancedToggleButtonVisible: Boolean = true, - override val advancedOptionsVisible: Boolean = false, - override val formPromptTaggedInput: Boolean = false, - override val prompt: String = "", - override val negativePrompt: String = "", - override val width: String = 512.toString(), - override val height: String = 512.toString(), - override val samplingSteps: Int = 20, - override val cfgScale: Float = 7f, - override val restoreFaces: Boolean = false, - override val seed: String = "", - override val subSeed: String = "", - override val subSeedStrength: Float = 0f, - override val selectedSampler: String = "", - override val selectedStylePreset: StabilityAiStylePreset = StabilityAiStylePreset.NONE, - override val selectedClipGuidancePreset: StabilityAiClipGuidance = StabilityAiClipGuidance.NONE, - override val openAiModel: OpenAiModel = OpenAiModel.DALL_E_2, - override val openAiSize: OpenAiSize = OpenAiSize.W1024_H1024, - override val openAiQuality: OpenAiQuality = OpenAiQuality.STANDARD, - override val openAiStyle: OpenAiStyle = OpenAiStyle.VIVID, - override val availableSamplers: List = emptyList(), - override val widthValidationError: UiText? = null, - override val heightValidationError: UiText? = null, - override val nsfw: Boolean = false, - override val batchCount: Int = 1, - override val generateButtonEnabled: Boolean = true, -) : GenerationMviState() { - - override fun copyState( - onBoardingDemo: Boolean, - screenModal: Modal, - mode: ServerSource, - advancedToggleButtonVisible: Boolean, - advancedOptionsVisible: Boolean, - formPromptTaggedInput: Boolean, - prompt: String, - negativePrompt: String, - width: String, - height: String, - samplingSteps: Int, - cfgScale: Float, - restoreFaces: Boolean, - seed: String, - subSeed: String, - subSeedStrength: Float, - selectedSampler: String, - availableSamplers: List, - selectedStylePreset: StabilityAiStylePreset, - selectedClipGuidancePreset: StabilityAiClipGuidance, - openAiModel: OpenAiModel, - openAiSize: OpenAiSize, - openAiQuality: OpenAiQuality, - openAiStyle: OpenAiStyle, - widthValidationError: UiText?, - heightValidationError: UiText?, - nsfw: Boolean, - batchCount: Int, - generateButtonEnabled: Boolean - ): GenerationMviState = copy( - onBoardingDemo = onBoardingDemo, - screenModal = screenModal, - mode = mode, - advancedToggleButtonVisible = advancedToggleButtonVisible, - advancedOptionsVisible = advancedOptionsVisible, - formPromptTaggedInput = formPromptTaggedInput, - prompt = prompt, - negativePrompt = negativePrompt, - width = width, - height = height, - samplingSteps = samplingSteps, - cfgScale = cfgScale, - restoreFaces = restoreFaces, - seed = seed, - subSeed = subSeed, - subSeedStrength = subSeedStrength, - selectedSampler = selectedSampler, - availableSamplers = availableSamplers, - selectedStylePreset = selectedStylePreset, - selectedClipGuidancePreset = selectedClipGuidancePreset, - openAiModel = openAiModel, - openAiSize = openAiSize, - openAiQuality = openAiQuality, - openAiStyle = openAiStyle, - widthValidationError = widthValidationError, - heightValidationError = heightValidationError, - nsfw = nsfw, - batchCount = batchCount, - generateButtonEnabled = generateButtonEnabled, - ) -} - -fun TextToImageState.mapToPayload(): TextToImagePayload = with(this) { - TextToImagePayload( - prompt = prompt.trim(), - negativePrompt = negativePrompt.trim(), - samplingSteps = samplingSteps, - cfgScale = cfgScale, - width = when (mode) { - ServerSource.OPEN_AI -> openAiSize.width - else -> width.toIntOrNull() ?: 64 - }, - height = when (mode) { - ServerSource.OPEN_AI -> openAiSize.height - else -> height.toIntOrNull() ?: 64 - }, - restoreFaces = restoreFaces, - seed = seed.trim(), - subSeed = subSeed.trim(), - subSeedStrength = subSeedStrength, - sampler = selectedSampler, - nsfw = if (mode == ServerSource.HORDE) nsfw else false, - batchCount = if (mode == ServerSource.LOCAL_MICROSOFT_ONNX) 1 else batchCount, - style = openAiStyle.key.takeIf { - mode == ServerSource.OPEN_AI && openAiModel == OpenAiModel.DALL_E_3 - }, - quality = openAiQuality.key.takeIf { - mode == ServerSource.OPEN_AI && openAiModel == OpenAiModel.DALL_E_3 - }, - openAiModel = openAiModel.takeIf { mode == ServerSource.OPEN_AI }, - stabilityAiClipGuidance = selectedClipGuidancePreset.takeIf { mode == ServerSource.STABILITY_AI }, - stabilityAiStylePreset = selectedStylePreset.takeIf { mode == ServerSource.STABILITY_AI }, - ) -} - -fun ValidationResult.mapToUi(): UiText? { - if (this.isValid) return null - return when (validationError as DimensionValidator.Error) { - DimensionValidator.Error.Empty -> LocalizationR.string.error_empty.asUiText() - is DimensionValidator.Error.LessThanMinimum -> UiText.Resource( - LocalizationR.string.error_min_size, - (validationError as DimensionValidator.Error.LessThanMinimum).min, - ) - is DimensionValidator.Error.BiggerThanMaximum -> UiText.Resource( - LocalizationR.string.error_max_size, - (validationError as DimensionValidator.Error.BiggerThanMaximum).max, - ) - else -> LocalizationR.string.error_invalid.asUiText() - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt deleted file mode 100755 index 06b3520fc..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.txt2img - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion -import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.interactor.wakelock.WakeLockInterActor -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.caching.SaveLastResultToCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.SaveGenerationResultUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase -import com.shifthackz.aisdv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.core.GenerationMviViewModel -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.android.core.mvi.EmptyEffect -import io.reactivex.rxjava3.kotlin.subscribeBy -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -class TextToImageViewModel( - dispatchersProvider: DispatchersProvider, - generationFormUpdateEvent: GenerationFormUpdateEvent, - getStableDiffusionSamplersUseCase: GetStableDiffusionSamplersUseCase, - observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, - saveLastResultToCacheUseCase: SaveLastResultToCacheUseCase, - saveGenerationResultUseCase: SaveGenerationResultUseCase, - interruptGenerationUseCase: InterruptGenerationUseCase, - mainRouter: MainRouter, - drawerRouter: DrawerRouter, - dimensionValidator: DimensionValidator, - private val textToImageUseCase: TextToImageUseCase, - private val schedulersProvider: SchedulersProvider, - private val preferenceManager: PreferenceManager, - private val notificationManager: PushNotificationManager, - private val wakeLockInterActor: WakeLockInterActor, - private val backgroundTaskManager: BackgroundTaskManager, - private val backgroundWorkObserver: BackgroundWorkObserver, - private val buildInfoProvider: BuildInfoProvider, -) : GenerationMviViewModel( - preferenceManager = preferenceManager, - getStableDiffusionSamplersUseCase = getStableDiffusionSamplersUseCase, - observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, - observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, - saveLastResultToCacheUseCase = saveLastResultToCacheUseCase, - saveGenerationResultUseCase = saveGenerationResultUseCase, - interruptGenerationUseCase = interruptGenerationUseCase, - mainRouter = mainRouter, - drawerRouter = drawerRouter, - dimensionValidator = dimensionValidator, - schedulersProvider = schedulersProvider, - backgroundWorkObserver = backgroundWorkObserver, -) { - - private val progressModal: Modal - get() = when (currentState.mode) { - ServerSource.LOCAL_MICROSOFT_ONNX, - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> { - Modal.Generating(canCancel = preferenceManager.localOnnxAllowCancel) - } - - else -> Modal.Communicating() - } - - override val initialState = TextToImageState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !generationFormUpdateEvent - .observeTxt2ImgForm() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onNext = { payload -> - (payload as? GenerationFormUpdateEvent.Payload.T2IForm) - ?.let(::updateFormPreviousAiGeneration) - ?.also { generationFormUpdateEvent.clear() } - }, - ) - } - - override fun generateDisposable() = currentState - .mapToPayload() - .let(textToImageUseCase::invoke) - .doOnSubscribe { - wakeLockInterActor.acquireWakelockUseCase() - setActiveModal(progressModal) - } - .doFinally { wakeLockInterActor.releaseWakeLockUseCase() } - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = { t -> - notificationManager.createAndShowInstant( - LocalizationR.string.notification_fail_title.asUiText(), - LocalizationR.string.notification_fail_sub_title.asUiText(), - ) - setActiveModal( - Modal.Error( - (t.localizedMessage ?: "Something went wrong").asUiText() - ) - ) - errorLog(t) - }, - onSuccess = { ai -> - notificationManager.createAndShowInstant( - LocalizationR.string.notification_finish_title.asUiText(), - LocalizationR.string.notification_finish_sub_title.asUiText(), - ) - setActiveModal( - Modal.Image.create( - list = ai, - autoSaveEnabled = preferenceManager.autoSaveAiResults, - reportEnabled = buildInfoProvider.type != BuildType.FOSS, - ) - ) - }, - ) - - override fun generateBackground() { - val payload = currentState.mapToPayload() - backgroundTaskManager.scheduleTextToImageTask(payload) - } - - override fun onReceivedHordeStatus(status: HordeProcessStatus) { - (currentState.screenModal as? Modal.Communicating) - ?.copy(hordeProcessStatus = status) - ?.let(::setActiveModal) - } - - override fun onReceivedLocalDiffusionStatus(status: LocalDiffusionStatus) { - (currentState.screenModal as? Modal.Generating) - ?.copy(status = status) - ?.let(::setActiveModal) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/SdaiWebViewClient.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/SdaiWebViewClient.kt deleted file mode 100644 index 220cd3631..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/SdaiWebViewClient.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.web - -import android.graphics.Bitmap -import android.webkit.WebResourceRequest -import android.webkit.WebView -import android.webkit.WebViewClient - -class SdaiWebViewClient( - private val onLoadingChanged: (Boolean) -> Unit = {}, -) : WebViewClient() { - - override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { - return false - } - - @Deprecated("Deprecated in Java", ReplaceWith("false")) - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - return false - } - - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - onLoadingChanged(true) - super.onPageStarted(view, url, favicon) - } - - override fun onPageFinished(view: WebView?, url: String?) { - onLoadingChanged(false) - super.onPageFinished(view, url) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiIntent.kt deleted file mode 100644 index d55d0ac31..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiIntent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.web.webui - -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface WebUiIntent : MviIntent { - data object NavigateBack : WebUiIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiState.kt deleted file mode 100644 index f3aa45858..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.web.webui - -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.android.core.mvi.MviState - -data class WebUiState( - val loading: Boolean = true, - val source: ServerSource = ServerSource.AUTOMATIC1111, - val url: String = "", -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiViewModel.kt deleted file mode 100644 index f17cbb22a..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiViewModel.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.shifthackz.aisdv1.presentation.screen.web.webui - -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.android.core.mvi.EmptyEffect - -class WebUiViewModel( - dispatchersProvider: DispatchersProvider, - private val mainRouter: MainRouter, - private val preferenceManager: PreferenceManager, -) : MviRxViewModel() { - - override val initialState: WebUiState = WebUiState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - updateState { state -> - state.copy( - loading = false, - source = preferenceManager.source, - url = when (preferenceManager.source) { - ServerSource.AUTOMATIC1111 -> preferenceManager.automatic1111ServerUrl - ServerSource.SWARM_UI -> preferenceManager.swarmUiServerUrl - else -> "" - } - ) - } - } - - override fun processIntent(intent: WebUiIntent) { - when (intent) { - WebUiIntent.NavigateBack -> mainRouter.navigateBack() - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppThemeState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppThemeState.kt deleted file mode 100644 index 19f84211f..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppThemeState.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.presentation.theme.global - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.android.core.mvi.MviState - -@Immutable -data class AiSdAppThemeState( - val stateKey: Long = System.currentTimeMillis(), - val systemColorPalette: Boolean = false, - val systemDarkTheme: Boolean = true, - val darkTheme: Boolean = true, - val colorToken: ColorToken = ColorToken.MAUVE, - val darkThemeToken: DarkThemeToken = DarkThemeToken.FRAPPE, -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppThemeViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppThemeViewModel.kt deleted file mode 100644 index 66a230853..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppThemeViewModel.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.shifthackz.aisdv1.presentation.theme.global - -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.android.core.mvi.EmptyEffect -import com.shifthackz.android.core.mvi.EmptyIntent -import io.reactivex.rxjava3.kotlin.subscribeBy - -class AiSdAppThemeViewModel( - preferenceManager: PreferenceManager, - dispatchersProvider: DispatchersProvider, - schedulersProvider: SchedulersProvider, - timeProvider: TimeProvider, -) : MviRxViewModel() { - - override val initialState = AiSdAppThemeState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !preferenceManager - .observe() - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda) { settings -> - updateState { state -> - state.copy( - stateKey = timeProvider.currentTimeMillis(), - systemColorPalette = settings.designUseSystemColorPalette, - systemDarkTheme = settings.designUseSystemDarkTheme, - darkTheme = settings.designDarkTheme, - colorToken = ColorToken.parse(settings.designColorToken), - darkThemeToken = DarkThemeToken.parse(settings.designDarkThemeToken), - ) - } - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt deleted file mode 100644 index 153a6e0a1..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.shifthackz.aisdv1.presentation.utils - -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute.HomeNavigation - -object Constants { - const val PAGINATION_PAYLOAD_SIZE = 1000 - const val DEBUG_MENU_ACCESS_TAPS = 7 - - const val SUB_SEED_STRENGTH_MIN = 0f - const val SUB_SEED_STRENGTH_MAX = 1f - - const val SAMPLING_STEPS_RANGE_MIN = 1 - const val SAMPLING_STEPS_RANGE_MAX = 150 - const val SAMPLING_STEPS_RANGE_STABILITY_AI_MAX = 50 - const val SAMPLING_STEPS_LOCAL_DIFFUSION_MAX = 50 - - const val BATCH_RANGE_MIN = 1 - const val BATCH_RANGE_MAX = 20 - - const val CFG_SCALE_RANGE_MIN = 1 - const val CFG_SCALE_RANGE_MAX = 35 - - const val DENOISING_STRENGTH_MIN = 0f - const val DENOISING_STRENGTH_MAX = 1f - - const val DRAW_CAP_RANGE_MIN = 1 - const val DRAW_CAP_RANGE_MAX = 60 - - const val MASK_BLUR_MIN = 1 - const val MASK_BLUR_MAX = 64 - - const val ONLY_MASKED_PADDING_MIN = 0 - const val ONLY_MASKED_PADDING_MAX = 256 - - const val EXTRA_MINIMUM = -10.0 - const val EXTRA_MAXIMUM = 10.0 - const val EXTRA_STEP = 0.25 - - const val MIME_TYPE_ZIP = "application/zip" - const val MIME_TYPE_JPG = "image/jpeg" - - const val HORDE_DEFAULT_API_KEY = "0000000000" - - val sizes = listOf("64", "128", "256", "320", "384", "448", "512") - - val homeRoutes = listOf( - HomeNavigation.TxtToImg, - HomeNavigation.ImgToImg, - HomeNavigation.Gallery, - HomeNavigation.Settings, - ) - - fun lora(alias: String) = "" -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/ReportProblemEmailComposer.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/ReportProblemEmailComposer.kt deleted file mode 100644 index 2f29dc76e..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/ReportProblemEmailComposer.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.shifthackz.aisdv1.presentation.utils - -import android.content.Context -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.FileLoggingTree -import com.shifthackz.aisdv1.core.sharing.shareEmail -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import java.io.File - -class ReportProblemEmailComposer : KoinComponent { - - private val fileProviderDescriptor: FileProviderDescriptor by inject() - private val buildInfoProvider: BuildInfoProvider by inject() - - fun invoke(context: Context) { - val logFile = File( - fileProviderDescriptor.logsCacheDirPath + - "/" + - FileLoggingTree.LOGGER_FILENAME - ) - context.shareEmail( - email = "sdai@moroz.cc", - subject = "SDAI - Problem report", - body = "SDAI : $buildInfoProvider", - file = if (!logFile.exists()) null else logFile, - fileProviderPath = fileProviderDescriptor.providerPath, - ) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityViewModel.kt deleted file mode 100644 index fb99f9578..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityViewModel.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.connectivity - -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.connectivity.ObserveSeverConnectivityUseCase -import com.shifthackz.android.core.mvi.EmptyEffect -import com.shifthackz.android.core.mvi.EmptyIntent -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.kotlin.subscribeBy - -class ConnectivityViewModel( - preferenceManager: PreferenceManager, - observeServerConnectivityUseCase: ObserveSeverConnectivityUseCase, - dispatchersProvider: DispatchersProvider, - schedulersProvider: SchedulersProvider, -) : MviRxViewModel() { - - override val initialState = ConnectivityState.Uninitialized(preferenceManager.monitorConnectivity) - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !Flowable.combineLatest( - observeServerConnectivityUseCase(), - preferenceManager.observe().map(Settings::monitorConnectivity), - ::Pair, - ) - .map(ConnectivityState::consume) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog, EmptyLambda, ::emitState) - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionComponent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionComponent.kt deleted file mode 100644 index 9799f9c8e..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionComponent.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.engine - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.widget.input.DropdownTextField -import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun EngineSelectionComponent( - modifier: Modifier = Modifier, -) { - MviComponent( - viewModel = koinViewModel(), - ) { state, intentHandler -> - when (state.mode) { - ServerSource.AUTOMATIC1111 -> DropdownTextField( - label = LocalizationR.string.hint_sd_model.asUiText(), - loading = state.loading, - modifier = modifier, - value = state.selectedSdModel, - items = state.sdModels, - onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, - ) - - ServerSource.SWARM_UI -> DropdownTextField( - label = LocalizationR.string.hint_sd_model.asUiText(), - loading = state.loading, - modifier = modifier, - value = state.selectedSwarmModel, - items = state.swarmModels, - onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, - ) - - ServerSource.HUGGING_FACE -> DropdownTextField( - label = LocalizationR.string.hint_hugging_face_model.asUiText(), - loading = state.loading, - modifier = modifier, - value = state.selectedHfModel, - items = state.hfModels, - onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, - ) - - ServerSource.STABILITY_AI -> DropdownTextField( - label = LocalizationR.string.hint_stability_ai_engine.asUiText(), - loading = state.loading, - modifier = modifier, - value = state.selectedStEngine, - items = state.stEngines, - onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, - ) - - ServerSource.LOCAL_MICROSOFT_ONNX -> DropdownTextField( - label = LocalizationR.string.hint_sd_model.asUiText(), - loading = state.loading, - modifier = modifier, - value = state.localAiModels.firstOrNull { it.id == state.selectedLocalAiModelId }, - items = state.localAiModels, - onItemSelected = { intentHandler(EngineSelectionIntent(it.id)) }, - displayDelegate = { it.name.asUiText() }, - ) - - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> Unit - ServerSource.HORDE -> Unit - ServerSource.OPEN_AI -> Unit - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionIntent.kt deleted file mode 100644 index 9875b587c..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionIntent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.engine - -import com.shifthackz.android.core.mvi.MviIntent - -data class EngineSelectionIntent(val value: String) : MviIntent diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionState.kt deleted file mode 100644 index 382a299df..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionState.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.engine - -import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.android.core.mvi.MviState - -@Immutable -data class EngineSelectionState( - val loading: Boolean = true, - val mode: ServerSource = ServerSource.AUTOMATIC1111, - val sdModels: List = emptyList(), - val selectedSdModel: String = "", - val swarmModels: List = emptyList(), - val selectedSwarmModel: String = "", - val hfModels: List = emptyList(), - val selectedHfModel: String = "", - val stEngines: List = emptyList(), - val selectedStEngine: String = "", - val localAiModels: List = emptyList(), - val selectedLocalAiModelId: String = "", -): MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionViewModel.kt deleted file mode 100644 index 9bb710022..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/engine/EngineSelectionViewModel.kt +++ /dev/null @@ -1,139 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.engine - -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.model.Hexagonal -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.downloadable.ObserveLocalOnnxModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.GetConfigurationUseCase -import com.shifthackz.aisdv1.domain.usecase.stabilityai.FetchAndGetStabilityAiEnginesUseCase -import com.shifthackz.aisdv1.domain.usecase.swarmmodel.FetchAndGetSwarmUiModelsUseCase -import com.shifthackz.android.core.mvi.EmptyEffect -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.kotlin.subscribeBy - -class EngineSelectionViewModel( - dispatchersProvider: DispatchersProvider, - fetchAndGetSwarmUiModelsUseCase: FetchAndGetSwarmUiModelsUseCase, - observeLocalOnnxModelsUseCase: ObserveLocalOnnxModelsUseCase, - fetchAndGetStabilityAiEnginesUseCase: FetchAndGetStabilityAiEnginesUseCase, - getHuggingFaceModelsUseCase: FetchAndGetHuggingFaceModelsUseCase, - private val preferenceManager: PreferenceManager, - private val schedulersProvider: SchedulersProvider, - private val getConfigurationUseCase: GetConfigurationUseCase, - private val selectStableDiffusionModelUseCase: SelectStableDiffusionModelUseCase, - private val getStableDiffusionModelsUseCase: GetStableDiffusionModelsUseCase, -) : MviRxViewModel() { - - override val initialState = EngineSelectionState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - val configuration = preferenceManager - .observe() - .flatMap { getConfigurationUseCase().toFlowable() } - .onErrorReturn { Configuration() } - - val a1111Models = getStableDiffusionModelsUseCase() - .onErrorReturn { emptyList() } - .toFlowable() - - val swarmModels = fetchAndGetSwarmUiModelsUseCase() - .onErrorReturn { emptyList() } - .toFlowable() - - val huggingFaceModels = getHuggingFaceModelsUseCase() - .onErrorReturn { emptyList() } - .toFlowable() - - val stabilityAiEngines = fetchAndGetStabilityAiEnginesUseCase() - .onErrorReturn { emptyList() } - .toFlowable() - - val localAiModels = observeLocalOnnxModelsUseCase() - .map { models -> models.filter { it.downloaded || it.id == LocalAiModel.CustomOnnx.id } } - .onErrorReturn { emptyList() } - - !Flowable.combineLatest( - configuration, - a1111Models, - swarmModels, - huggingFaceModels, - stabilityAiEngines, - localAiModels, - ::Hexagonal, - ) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy( - onError = ::errorLog, - onComplete = EmptyLambda, - onNext = { (config, sdModels, swarmModels, hfModels, stEngines, localModels) -> - updateState { state -> - state.copy( - loading = false, - mode = config.source, - sdModels = sdModels.map { it.first.title }, - selectedSdModel = sdModels.firstOrNull { it.second }?.first?.title - ?: state.selectedSdModel, - swarmModels = swarmModels.map { it.name }, - selectedSwarmModel = swarmModels.firstOrNull { it.name == config.swarmUiModel }?.name - ?: state.selectedSwarmModel, - hfModels = hfModels.map { it.alias }, - selectedHfModel = config.huggingFaceModel, - stEngines = stEngines.map { it.id }, - selectedStEngine = config.stabilityAiEngineId, - localAiModels = localModels, - selectedLocalAiModelId = localModels.firstOrNull { it.id == config.localOnnxModelId }?.id - ?: state.selectedLocalAiModelId - ) - } - }, - ) - } - - override fun processIntent(intent: EngineSelectionIntent) { - when (currentState.mode) { - ServerSource.AUTOMATIC1111 -> !selectStableDiffusionModelUseCase(intent.value) - .doOnSubscribe { - updateState { - it.copy( - loading = true, - selectedSdModel = intent.value, - ) - } - } - .andThen(getStableDiffusionModelsUseCase()) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { sdModels -> - updateState { state -> - state.copy( - loading = false, - sdModels = sdModels.map { it.first.title }, - selectedSdModel = sdModels.first { it.second }.first.title, - ) - } - } - - ServerSource.SWARM_UI -> preferenceManager.swarmUiModel = intent.value - - ServerSource.HUGGING_FACE -> preferenceManager.huggingFaceModel = intent.value - - ServerSource.STABILITY_AI -> preferenceManager.stabilityAiEngineId = intent.value - - ServerSource.LOCAL_MICROSOFT_ONNX -> preferenceManager.localOnnxModelId = intent.value - - else -> Unit - } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/image/ZoomableImage.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/image/ZoomableImage.kt deleted file mode 100644 index bb955d1d3..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/image/ZoomableImage.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.image - -import android.graphics.Bitmap -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTransformGestures -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.BlurEffect -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import com.shifthackz.aisdv1.presentation.R as PresentationR - -sealed interface ZoomableImageSource { - data class Bmp(val bitmap: Bitmap) : ZoomableImageSource - data class Resource(@DrawableRes val resId: Int) : ZoomableImageSource -} - -/** - * Allows to implement image zoom pinch, rotate behavior gestures - * - * Source: https://stackoverflow.com/questions/66005066/android-jetpack-compose-how-to-zoom-a-image-in-a-box - */ -@Composable -fun ZoomableImage( - modifier: Modifier = Modifier, - source: ZoomableImageSource, - backgroundColor: Color = MaterialTheme.colorScheme.background, - minScale: Float = 1f, - maxScale: Float = 6f, - hideImage: Boolean = false, - hideBlurRadius: Float = 69f, -) { - val configuration = LocalConfiguration.current - val width = configuration.screenWidthDp - - val scale = remember { mutableFloatStateOf(calculateInitialScale(source, width)) } -// val rotationState = remember { mutableStateOf(1f) } - - var offsetX by remember { mutableFloatStateOf(0f) } - var offsetY by remember { mutableFloatStateOf(0f) } - - Box( - modifier = modifier - .clip(RectangleShape) - .fillMaxSize() - .background(backgroundColor) - .pointerInput(Unit) { - detectTransformGestures { _, pan, zoom, _ -> - scale.value *= zoom - //rotationState.value += rotation - offsetX += pan.x * zoom - offsetY += pan.y * zoom - } - }, - ) { - val imageModifier = Modifier - .align(Alignment.Center) - .graphicsLayer( - scaleX = maxOf(minScale, minOf(maxScale, scale.floatValue)), - scaleY = maxOf(minScale, minOf(maxScale, scale.floatValue)), - //rotationZ = rotationState.value, - translationX = offsetX, - translationY = offsetY, - ) - .then( - if (!hideImage) Modifier - else Modifier.graphicsLayer { - renderEffect = BlurEffect( - radiusX = hideBlurRadius, - radiusY = hideBlurRadius, - ) - } - ) - - when (source) { - is ZoomableImageSource.Bmp -> Image( - modifier = imageModifier, - contentDescription = null, - bitmap = source.bitmap.asImageBitmap(), - ) - - is ZoomableImageSource.Resource -> Image( - modifier = imageModifier, - contentDescription = null, - painter = painterResource(id = source.resId), - ) - } -// if (hideImage) { -// Icon( -// modifier = Modifier -// .size(28.dp) -// .align(Alignment.Center), -// imageVector = Icons.Default.VisibilityOff, -// contentDescription = "hidden", -// tint = MaterialTheme.colorScheme.primary, -// ) -// } - } -} - -private fun calculateInitialScale( - source: ZoomableImageSource, - width: Int, -): Float { - if (source is ZoomableImageSource.Bmp) { - val initialScale = (width.toFloat() / source.bitmap.width.toFloat()) - return initialScale + 1f - } - return 1f -} - -@Preview -@Composable -private fun ZoomableImagePreview() { - ZoomableImage( - modifier = Modifier.fillMaxSize(), - source = ZoomableImageSource.Resource(PresentationR.drawable.ic_gallery) - ) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/GenerationInputForm.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/GenerationInputForm.kt deleted file mode 100644 index ec49f4ec2..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/GenerationInputForm.kt +++ /dev/null @@ -1,615 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.input - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.material.icons.filled.ArrowDropUp -import androidx.compose.material.icons.filled.Casino -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.math.roundTo -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.OpenAiQuality -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.OpenAiStyle -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.StabilityAiClipGuidance -import com.shifthackz.aisdv1.domain.entity.StabilityAiSampler -import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.core.GenerationMviState -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.theme.sliderColors -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.aisdv1.presentation.utils.Constants.BATCH_RANGE_MAX -import com.shifthackz.aisdv1.presentation.utils.Constants.BATCH_RANGE_MIN -import com.shifthackz.aisdv1.presentation.utils.Constants.CFG_SCALE_RANGE_MAX -import com.shifthackz.aisdv1.presentation.utils.Constants.CFG_SCALE_RANGE_MIN -import com.shifthackz.aisdv1.presentation.utils.Constants.SAMPLING_STEPS_LOCAL_DIFFUSION_MAX -import com.shifthackz.aisdv1.presentation.utils.Constants.SAMPLING_STEPS_RANGE_MAX -import com.shifthackz.aisdv1.presentation.utils.Constants.SAMPLING_STEPS_RANGE_MIN -import com.shifthackz.aisdv1.presentation.utils.Constants.SAMPLING_STEPS_RANGE_STABILITY_AI_MAX -import com.shifthackz.aisdv1.presentation.utils.Constants.SUB_SEED_STRENGTH_MAX -import com.shifthackz.aisdv1.presentation.utils.Constants.SUB_SEED_STRENGTH_MIN -import com.shifthackz.aisdv1.presentation.widget.engine.EngineSelectionComponent -import com.shifthackz.aisdv1.presentation.widget.input.chip.ChipTextFieldEvent -import com.shifthackz.aisdv1.presentation.widget.input.chip.ChipTextFieldWithItem -import kotlin.math.abs -import kotlin.math.absoluteValue -import kotlin.math.roundToInt -import kotlin.random.Random -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun GenerationInputForm( - modifier: Modifier = Modifier, - state: GenerationMviState, - isImg2Img: Boolean = false, - promptChipTextFieldState: MutableState, - negativePromptChipTextFieldState: MutableState, - processIntent: (GenerationMviIntent) -> Unit = {}, - afterSlidersSection: @Composable () -> Unit = {}, -) { - @Composable - fun batchComponent() { - Text( - modifier = Modifier.padding(top = 8.dp), - text = stringResource( - id = LocalizationR.string.hint_batch, - "${state.batchCount}", - ), - ) - SliderTextInputField( - value = state.batchCount * 1f, - valueRange = (BATCH_RANGE_MIN * 1f)..(BATCH_RANGE_MAX * 1f), - valueDiff = 1f, - fractionDigits = 0, - steps = abs(BATCH_RANGE_MIN - BATCH_RANGE_MAX) - 1, - sliderColors = sliderColors, - onValueChange = { processIntent(GenerationMviIntent.Update.Batch(it.roundToInt())) }, - ) - } - - @Composable - fun RowScope.sizeTextFieldsComponent(modifier: Modifier = Modifier) { - TextField( - modifier = modifier.padding(end = 4.dp), - value = state.width, - onValueChange = { value -> - if (value.length <= 4) { - value - .filter { it.isDigit() } - .let(GenerationMviIntent.Update.Size::Width) - .let(processIntent::invoke) - } - }, - isError = state.widthValidationError != null, - supportingText = { - state.widthValidationError?.let { - Text( - it.asString(), - color = MaterialTheme.colorScheme.error - ) - } - }, - label = { Text(stringResource(id = LocalizationR.string.width)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - colors = textFieldColors, - ) - TextField( - modifier = modifier.padding(start = 4.dp), - value = state.height, - onValueChange = { value -> - if (value.length <= 4) { - value - .filter { it.isDigit() } - .let(GenerationMviIntent.Update.Size::Height) - .let(processIntent::invoke) - } - }, - isError = state.heightValidationError != null, - supportingText = { - state.heightValidationError?.let { - Text( - it.asString(), - color = MaterialTheme.colorScheme.error - ) - } - }, - label = { Text(stringResource(id = LocalizationR.string.height)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - colors = textFieldColors, - ) - } - - Column(modifier = modifier) { - if (!state.onBoardingDemo) { - when (state.mode) { - ServerSource.AUTOMATIC1111, - ServerSource.SWARM_UI, - ServerSource.STABILITY_AI, - ServerSource.HUGGING_FACE, - ServerSource.LOCAL_MICROSOFT_ONNX -> EngineSelectionComponent( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) - - ServerSource.OPEN_AI -> DropdownTextField( - modifier = Modifier.padding(top = 8.dp), - label = LocalizationR.string.hint_model_open_ai.asUiText(), - value = state.openAiModel, - items = OpenAiModel.entries, - onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Model(it)) }, - ) - - else -> Unit - } - } - if (state.formPromptTaggedInput) { - ChipTextFieldWithItem( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - textFieldValueState = promptChipTextFieldState, - label = LocalizationR.string.hint_prompt, - list = state.promptKeywords, - onItemClick = { _, tag -> - processIntent( - GenerationMviIntent.SetModal( - Modal.EditTag( - prompt = state.prompt, - negativePrompt = state.negativePrompt, - tag = tag, - isNegative = false, - ) - ) - ) - }, - ) { event -> - val prompt = processTaggedPrompt(state.promptKeywords, event) - processIntent(GenerationMviIntent.Update.Prompt(prompt)) - } - } else { - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - value = state.prompt, - onValueChange = { processIntent(GenerationMviIntent.Update.Prompt(it)) }, - label = { Text(stringResource(id = LocalizationR.string.hint_prompt)) }, - colors = textFieldColors, - ) - } - - // Horde does not support "negative prompt" - when (state.mode) { - ServerSource.AUTOMATIC1111, - ServerSource.SWARM_UI, - ServerSource.HUGGING_FACE, - ServerSource.STABILITY_AI, - ServerSource.LOCAL_MICROSOFT_ONNX -> { - if (state.formPromptTaggedInput) { - ChipTextFieldWithItem( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - textFieldValueState = negativePromptChipTextFieldState, - label = LocalizationR.string.hint_prompt_negative, - list = state.negativePromptKeywords, - onItemClick = { _, tag -> - processIntent( - GenerationMviIntent.SetModal( - Modal.EditTag( - prompt = state.prompt, - negativePrompt = state.negativePrompt, - tag = tag, - isNegative = true, - ) - ) - ) - }, - ) { event -> - val prompt = processTaggedPrompt(state.negativePromptKeywords, event) - processIntent(GenerationMviIntent.Update.NegativePrompt(prompt)) - } - } else { - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - value = state.negativePrompt, - onValueChange = { processIntent(GenerationMviIntent.Update.NegativePrompt(it)) }, - label = { Text(stringResource(id = LocalizationR.string.hint_prompt_negative)) }, - colors = textFieldColors, - ) - } - } - - else -> Unit - } - - // Size input fields - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) { - val localModifier = Modifier.weight(1f) - - when (state.mode) { - ServerSource.HORDE, - ServerSource.LOCAL_MICROSOFT_ONNX -> { - DropdownTextField( - modifier = localModifier.padding(end = 4.dp), - label = LocalizationR.string.width.asUiText(), - value = state.width, - items = Constants.sizes, - onItemSelected = { processIntent(GenerationMviIntent.Update.Size.Width(it)) }, - ) - DropdownTextField( - modifier = localModifier.padding(start = 4.dp), - label = LocalizationR.string.height.asUiText(), - value = state.height, - items = Constants.sizes, - onItemSelected = { processIntent(GenerationMviIntent.Update.Size.Height(it)) }, - ) - } - - ServerSource.AUTOMATIC1111, - ServerSource.SWARM_UI, - ServerSource.HUGGING_FACE -> { - sizeTextFieldsComponent(localModifier) - } - - ServerSource.STABILITY_AI -> { - if (isImg2Img) Unit - else sizeTextFieldsComponent(localModifier) - } - - ServerSource.OPEN_AI -> { - DropdownTextField( - label = LocalizationR.string.hint_image_size.asUiText(), - value = state.openAiSize, - items = OpenAiSize.entries.filter { - it.supportedModels.contains(state.openAiModel) - }, - onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Size(it)) }, - displayDelegate = { it.key.asUiText() }, - ) - } - else -> Unit - } - } - - if (state.mode == ServerSource.OPEN_AI) { - if (state.openAiModel == OpenAiModel.DALL_E_3) { - DropdownTextField( - modifier = Modifier.padding(top = 8.dp), - label = LocalizationR.string.hint_quality.asUiText(), - value = state.openAiQuality, - items = OpenAiQuality.entries, - onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Quality(it)) }, - ) - DropdownTextField( - modifier = Modifier.padding(top = 8.dp), - label = LocalizationR.string.hint_style.asUiText(), - value = state.openAiStyle, - items = OpenAiStyle.entries, - onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Style(it)) }, - ) - } - batchComponent() - } - - if (state.advancedToggleButtonVisible && state.mode != ServerSource.OPEN_AI) { - TextButton( - modifier = Modifier.align(Alignment.CenterHorizontally), - onClick = { - processIntent( - GenerationMviIntent.SetAdvancedOptionsVisibility(!state.advancedOptionsVisible) - ) - }, - ) { - Icon( - imageVector = if (state.advancedOptionsVisible) Icons.Default.ArrowDropUp - else Icons.Default.ArrowDropDown, - contentDescription = null, - ) - Text( - text = stringResource( - id = if (state.advancedOptionsVisible) LocalizationR.string.action_options_hide - else LocalizationR.string.action_options_show - ) - ) - } - } - - AnimatedVisibility( - visible = state.advancedOptionsVisible && state.mode != ServerSource.OPEN_AI, - ) { - Column { - // Sampler selection only supported for A1111, STABILITY AI - when (state.mode) { - ServerSource.STABILITY_AI, - ServerSource.AUTOMATIC1111 -> DropdownTextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - label = LocalizationR.string.hint_sampler.asUiText(), - value = state.selectedSampler, - items = state.availableSamplers, - onItemSelected = { processIntent(GenerationMviIntent.Update.Sampler(it)) }, - displayDelegate = { value -> - if (value == StabilityAiSampler.NONE.toString()) { - LocalizationR.string.hint_autodetect.asUiText() - } else { - value.asUiText() - } - } - ) - - else -> Unit - } - // Style-preset only for Stablity AI - if (state.mode == ServerSource.STABILITY_AI) { - DropdownTextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - label = LocalizationR.string.hint_style_preset.asUiText(), - value = state.selectedStylePreset, - items = StabilityAiStylePreset.entries, - onItemSelected = { processIntent(GenerationMviIntent.Update.StabilityAi.Style(it)) }, - displayDelegate = { value -> - if (value == StabilityAiStylePreset.NONE) { - LocalizationR.string.hint_autodetect.asUiText() - } else { - value.key.asUiText() - } - }, - ) - DropdownTextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - label = LocalizationR.string.hint_clip_guidance_preset.asUiText(), - value = state.selectedClipGuidancePreset, - items = StabilityAiClipGuidance.entries, - onItemSelected = { processIntent(GenerationMviIntent.Update.StabilityAi.ClipGuidance(it)) }, - ) - } - - // Seed is not available for Hugging Face - if (state.mode != ServerSource.OPEN_AI) { - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - value = state.seed, - onValueChange = { value -> - value - .filter { it.isDigit() } - .let(GenerationMviIntent.Update::Seed) - .let(processIntent::invoke) - }, - label = { Text(stringResource(id = LocalizationR.string.hint_seed)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - trailingIcon = { - IconButton(onClick = { - processIntent(GenerationMviIntent.Update.Seed("${Random.nextLong().absoluteValue}")) - }) { - Icon( - imageVector = Icons.Default.Casino, - contentDescription = "Random", - ) - } - }, - colors = textFieldColors, - ) - } - // NSFW flag specifically for Horde API - if (state.mode == ServerSource.HORDE) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Switch( - checked = state.nsfw, - onCheckedChange = { processIntent(GenerationMviIntent.Update.Nsfw(it)) }, - ) - Text( - modifier = Modifier.padding(horizontal = 8.dp), - text = stringResource(id = LocalizationR.string.hint_nsfw), - ) - } - } - // Variation seed supported for A1111, SwarmUI - when (state.mode) { - ServerSource.AUTOMATIC1111, - ServerSource.SWARM_UI -> TextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - value = state.subSeed, - onValueChange = { value -> - value - .filter { it.isDigit() } - .let(GenerationMviIntent.Update::SubSeed) - .let(processIntent::invoke) - }, - label = { Text(stringResource(id = LocalizationR.string.hint_sub_seed)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - trailingIcon = { - IconButton(onClick = { - processIntent(GenerationMviIntent.Update.SubSeed("${Random.nextLong().absoluteValue}")) - }) { - Icon( - imageVector = Icons.Default.Casino, - contentDescription = "Random", - ) - } - }, - colors = textFieldColors, - ) - - else -> Unit - } - // Sub-seed strength is not available for Local Diffusion - when (state.mode) { - ServerSource.AUTOMATIC1111, - ServerSource.SWARM_UI, - ServerSource.HORDE -> { - Text( - modifier = Modifier.padding(top = 8.dp), - text = stringResource( - id = LocalizationR.string.hint_sub_seed_strength, - "${state.subSeedStrength.roundTo(2)}", - ), - ) - SliderTextInputField( - value = state.subSeedStrength, - valueRange = SUB_SEED_STRENGTH_MIN..SUB_SEED_STRENGTH_MAX, - valueDiff = 0.01f, - sliderColors = sliderColors, - onValueChange = { - processIntent(GenerationMviIntent.Update.SubSeedStrength(it)) - }, - ) - } - - else -> Unit - } - - //Steps not available for open ai - if (state.mode != ServerSource.OPEN_AI) { - val stepsMax = when (state.mode) { - ServerSource.LOCAL_MICROSOFT_ONNX -> SAMPLING_STEPS_LOCAL_DIFFUSION_MAX - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> SAMPLING_STEPS_LOCAL_DIFFUSION_MAX - ServerSource.STABILITY_AI -> SAMPLING_STEPS_RANGE_STABILITY_AI_MAX - else -> SAMPLING_STEPS_RANGE_MAX - } - val steps = state.samplingSteps.coerceIn(SAMPLING_STEPS_RANGE_MIN, stepsMax) - Text( - modifier = Modifier.padding(top = 8.dp), - text = stringResource(id = LocalizationR.string.hint_sampling_steps, "$steps"), - ) - SliderTextInputField( - value = steps * 1f, - valueRange = (SAMPLING_STEPS_RANGE_MIN * 1f)..(stepsMax * 1f), - valueDiff = 1f, - steps = abs(stepsMax - SAMPLING_STEPS_RANGE_MIN) - 1, - sliderColors = sliderColors, - fractionDigits = 0, - onValueChange = { - processIntent(GenerationMviIntent.Update.SamplingSteps(it.roundToInt())) - }, - ) - } - - // CFG scale not available on open ai and google media pipe - when (state.mode) { - ServerSource.OPEN_AI, - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> Unit - else -> { - Text( - modifier = Modifier.padding(top = 8.dp), - text = stringResource( - LocalizationR.string.hint_cfg_scale, - "${state.cfgScale.roundTo(2)}", - ), - ) - SliderTextInputField( - value = state.cfgScale, - valueRange = (CFG_SCALE_RANGE_MIN * 1f)..(CFG_SCALE_RANGE_MAX * 1f), - valueDiff = 0.5f, - steps = abs(CFG_SCALE_RANGE_MAX - CFG_SCALE_RANGE_MIN) * 2 - 1, - sliderColors = sliderColors, - onValueChange = { - processIntent(GenerationMviIntent.Update.CfgScale(it)) - }, - ) - } - } - - when (state.mode) { - ServerSource.AUTOMATIC1111, - ServerSource.SWARM_UI, - ServerSource.STABILITY_AI, - ServerSource.HORDE -> afterSlidersSection() - - else -> Unit - } - - // Batch is not available for any Local - when (state.mode) { - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE, ServerSource.LOCAL_MICROSOFT_ONNX -> Unit - else -> batchComponent() - } - //Restore faces available only for A1111 - if (state.mode == ServerSource.AUTOMATIC1111) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Switch( - checked = state.restoreFaces, - onCheckedChange = { - processIntent(GenerationMviIntent.Update.RestoreFaces(it)) - }, - ) - Text( - modifier = Modifier.padding(horizontal = 8.dp), - text = stringResource(id = LocalizationR.string.hint_restore_faces), - ) - } - } - } - } - } -} - -private fun processTaggedPrompt(keywords: List, event: ChipTextFieldEvent): String { - val newKeywords = when (event) { - is ChipTextFieldEvent.Add -> buildList { - addAll(keywords) - add(event.item) - } - - is ChipTextFieldEvent.AddBatch -> buildList { - addAll(keywords) - addAll(event.items) - } - - is ChipTextFieldEvent.Remove -> keywords.filterIndexed { i, _ -> i != event.index } - is ChipTextFieldEvent.Update -> keywords.mapIndexed { i, s -> if (i == event.index) event.item else s } - } - return newKeywords.joinToString(", ") -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/source/ServerSourceLabel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/source/ServerSourceLabel.kt deleted file mode 100644 index 4ee15fd44..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/source/ServerSourceLabel.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.source - -import androidx.compose.runtime.Composable -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun ServerSource.getName(): String { - return getNameUiText().asString() -} - -fun ServerSource.getNameUiText(): UiText = when (this) { - ServerSource.AUTOMATIC1111 -> LocalizationR.string.srv_type_own - ServerSource.HORDE -> LocalizationR.string.srv_type_horde - ServerSource.LOCAL_MICROSOFT_ONNX -> LocalizationR.string.srv_type_local - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> LocalizationR.string.srv_type_media_pipe - ServerSource.HUGGING_FACE -> LocalizationR.string.srv_type_hugging_face - ServerSource.OPEN_AI -> LocalizationR.string.srv_type_open_ai - ServerSource.STABILITY_AI -> LocalizationR.string.srv_type_stability_ai - ServerSource.SWARM_UI -> LocalizationR.string.srv_type_swarm_ui -}.asUiText() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkIntent.kt deleted file mode 100644 index 064439d65..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkIntent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.work - -import com.shifthackz.android.core.mvi.MviIntent - -sealed interface BackgroundWorkIntent : MviIntent { - data object Dismiss : BackgroundWorkIntent -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkState.kt deleted file mode 100644 index 2f7cbe460..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkState.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.work - -import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.android.core.mvi.MviState - -data class BackgroundWorkState( - val visible: Boolean = false, - val title: UiText = UiText.empty, - val subTitle: UiText = UiText.empty, - val bitmap: Bitmap? = null, - val isError: Boolean = false, -) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkViewModel.kt deleted file mode 100644 index 0202687a6..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkViewModel.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.work - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.BackgroundWorkResult -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.android.core.mvi.EmptyEffect -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.kotlin.subscribeBy -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -class BackgroundWorkViewModel( - dispatchersProvider: DispatchersProvider, - private val backgroundWorkObserver: BackgroundWorkObserver, - private val schedulersProvider: SchedulersProvider, - private val base64ToBitmapConverter: Base64ToBitmapConverter, -) : MviRxViewModel() { - - override val initialState = BackgroundWorkState() - - override val effectDispatcher = dispatchersProvider.immediate - - init { - !Flowable.combineLatest( - backgroundWorkObserver.observeStatus(), - backgroundWorkObserver.observeResult(), - ::Pair, - ) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { (work, result) -> - updateState { state -> - val resultTitle = when (result) { - is BackgroundWorkResult.Error -> LocalizationR.string.notification_fail_title.asUiText() - is BackgroundWorkResult.Success -> LocalizationR.string.notification_finish_title.asUiText() - else -> UiText.empty - } - (result as? BackgroundWorkResult.Success) - ?.ai - ?.firstOrNull() - ?.image - ?.also(::setBitmap) - state.copy( - visible = work.running || result !is BackgroundWorkResult.None, - title = if (work.running) work.statusTitle.asUiText() else resultTitle, - subTitle = if (work.running) work.statusSubTitle.asUiText() else UiText.empty, - isError = !work.running && result is BackgroundWorkResult.Error, - bitmap = null, - ) - } - } - } - - override fun processIntent(intent: BackgroundWorkIntent) { - when (intent) { - BackgroundWorkIntent.Dismiss -> { - updateState { it.copy(visible = false, isError = false, bitmap = null) } - backgroundWorkObserver.dismissResult() - } - } - } - - private fun setBitmap(base64: String) = !base64ToBitmapConverter(Base64ToBitmapConverter.Input(base64)) - .map(Base64ToBitmapConverter.Output::bitmap) - .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) { bmp -> - updateState { it.copy(bitmap = bmp) } - } -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkWidget.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkWidget.kt deleted file mode 100644 index 31bd4a488..000000000 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/work/BackgroundWorkWidget.kt +++ /dev/null @@ -1,156 +0,0 @@ -package com.shifthackz.aisdv1.presentation.widget.work - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AutoFixNormal -import androidx.compose.material.icons.filled.Error -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.android.core.mvi.MviComponent -import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR - -@Composable -fun BackgroundWorkWidget( - modifier: Modifier = Modifier, -) { - MviComponent( - viewModel = koinViewModel(), - ) { state, processIntent -> - BackgroundWorkWidgetContent( - modifier = modifier, - state = state, - processIntent = processIntent, - ) - } -} - -@Composable -@Preview -private fun BackgroundWorkWidgetContent( - modifier: Modifier = Modifier, - state: BackgroundWorkState = BackgroundWorkState(), - processIntent: (BackgroundWorkIntent) -> Unit = {}, -) { - AnimatedVisibility( - modifier = modifier.fillMaxWidth(), - visible = state.visible, - enter = fadeIn(), - exit = fadeOut(), - ) { - val shape = RoundedCornerShape(16.dp) - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.Center, - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .background(MaterialTheme.colorScheme.surfaceTint, shape) - .clip(shape) - .padding(horizontal = 16.dp, vertical = 4.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Box( - modifier = Modifier - .size(40.dp) - .aspectRatio(1f) - .clip(RoundedCornerShape(4.dp)), - contentAlignment = Alignment.Center, - ) { - if (state.visible && !state.isError && state.bitmap == null) { - CircularProgressIndicator( - modifier = Modifier - .size(40.dp) - .aspectRatio(1f), - ) - Icon( - modifier = Modifier.size(16.dp), - imageVector = Icons.Default.AutoFixNormal, - contentDescription = "Imagine", - tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f), - ) - } - if (state.visible && state.isError && state.bitmap == null) { - Icon( - modifier = Modifier.size(16.dp), - imageVector = Icons.Default.Error, - contentDescription = "Error", - tint = MaterialTheme.colorScheme.primary, - ) - } - state.bitmap?.takeIf { !state.isError }?.let { - Image( - modifier = Modifier.fillMaxSize(), - bitmap = it.asImageBitmap(), - contentDescription = null, - ) - } - } - Column { - Text( - text = state.title.asString().takeIf(String::isNotBlank) - ?: stringResource(id = LocalizationR.string.notification_pending_title), - style = MaterialTheme.typography.titleMedium, - ) - state.subTitle.asString().takeIf(String::isNotBlank)?.let { subTitle -> - Text( - text = subTitle, - style = MaterialTheme.typography.bodyMedium, - ) - } - } - if (state.isError || state.bitmap != null) { - Spacer(modifier = Modifier.weight(1f)) - TextButton(onClick = { processIntent(BackgroundWorkIntent.Dismiss) }) { - Text( - text = stringResource(id = LocalizationR.string.ok), - ) - } - } - } - } - } -} - -@Composable -@Preview -private fun ContentPreview() { - BackgroundWorkWidgetContent( - state = BackgroundWorkState( - true, - "Header".asUiText(), - "This is status message.\nThat is indeed multiline.".asUiText(), - ), - ) -} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionActivity.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionActivity.kt similarity index 78% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionActivity.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionActivity.kt index 8e2cc4d00..f342598b3 100755 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionActivity.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionActivity.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.activity +package dev.minios.pdaiv1.presentation.activity import android.animation.ObjectAnimator import android.os.Bundle @@ -22,14 +22,15 @@ import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.shifthackz.aisdv1.core.common.log.debugLog -import com.shifthackz.aisdv1.presentation.extensions.navigatePopUpToCurrent -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.aisdv1.presentation.navigation.graph.mainNavGraph -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerScreen -import com.shifthackz.aisdv1.presentation.theme.global.AiSdAppTheme -import com.shifthackz.aisdv1.presentation.utils.PermissionUtil +import dev.minios.pdaiv1.core.common.log.debugLog +import dev.minios.pdaiv1.presentation.extensions.navigatePopUpToCurrent +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.navigation.SharedTransitionProvider +import dev.minios.pdaiv1.presentation.navigation.graph.mainNavGraph +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerScreen +import dev.minios.pdaiv1.presentation.theme.global.AiSdAppTheme +import dev.minios.pdaiv1.presentation.utils.PermissionUtil import com.shifthackz.android.core.mvi.MviComponent import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.viewModel @@ -129,19 +130,21 @@ class AiStableDiffusionActivity : AppCompatActivity() { } } ) { state -> - DrawerScreen( - drawerState = drawerState, - backStackEntry = backStackEntry, - homeRouteEntry = homeRouteEntry, - onRootNavigate = navController::navigate, - onHomeNavigate = { viewModel.processIntent(AppIntent.HomeRoute(it)) }, - navItems = state.drawerItems, - ) { - NavHost( - navController = navController, - startDestination = NavigationRoute.Splash, - builder = { mainNavGraph() }, - ) + SharedTransitionProvider { + DrawerScreen( + drawerState = drawerState, + backStackEntry = backStackEntry, + homeRouteEntry = homeRouteEntry, + onRootNavigate = navController::navigate, + onHomeNavigate = { viewModel.processIntent(AppIntent.HomeRoute(it)) }, + navItems = state.drawerItems, + ) { + NavHost( + navController = navController, + startDestination = NavigationRoute.Splash, + builder = { mainNavGraph() }, + ) + } } } } diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionViewModel.kt new file mode 100644 index 000000000..fc9f52128 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionViewModel.kt @@ -0,0 +1,66 @@ +package dev.minios.pdaiv1.presentation.activity + +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.graph.mainDrawerNavItems +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.home.HomeRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import io.reactivex.rxjava3.kotlin.subscribeBy + +class AiStableDiffusionViewModel( + dispatchersProvider: DispatchersProvider, + schedulersProvider: SchedulersProvider, + mainRouter: MainRouter, + drawerRouter: DrawerRouter, + private val homeRouter: HomeRouter, + private val preferenceManager: PreferenceManager, +) : MviRxViewModel() { + + override val initialState = AppState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !mainRouter.observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) + + !drawerRouter.observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) + + !homeRouter.observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) + + !preferenceManager.observe() + .map(::mainDrawerNavItems) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda) { drawerItems -> + updateState { state -> + state.copy(drawerItems = drawerItems) + } + } + } + + override fun processIntent(intent: AppIntent) = when (intent) { + AppIntent.GrantStoragePermission -> { + preferenceManager.saveToMediaStore = true + } + + is AppIntent.HomeRoute -> { + homeRouter.navigateToRoute(intent.navRoute) + } + + AppIntent.HideSplash -> updateState { state -> + state.copy(isShowSplash = false) + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AppIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AppIntent.kt new file mode 100644 index 000000000..6a3356e2e --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AppIntent.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.presentation.activity + +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface AppIntent : MviIntent { + + data object GrantStoragePermission : AppIntent + data object HideSplash : AppIntent + + data class HomeRoute(val navRoute: NavigationRoute) : AppIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AppState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AppState.kt new file mode 100644 index 000000000..12722e664 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/activity/AppState.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.presentation.activity + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.presentation.model.NavItem +import dev.minios.pdaiv1.presentation.navigation.graph.mainDrawerNavItems +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class AppState( + val drawerItems: List = mainDrawerNavItems(), + val isShowSplash: Boolean = true, +) : MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/components/DraggableScrollbar.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/components/DraggableScrollbar.kt new file mode 100644 index 000000000..91cd96800 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/components/DraggableScrollbar.kt @@ -0,0 +1,159 @@ +package dev.minios.pdaiv1.presentation.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectVerticalDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import kotlin.math.roundToInt + +/** + * Draggable scrollbar for fast navigation in gallery grid. + * Shows current scroll position and allows quick scrolling by dragging. + */ +@Composable +fun DraggableScrollbar( + lazyGridState: LazyGridState, + totalItems: Int, + columns: Int, + modifier: Modifier = Modifier, +) { + val density = LocalDensity.current + val coroutineScope = rememberCoroutineScope() + + var containerHeight by remember { mutableFloatStateOf(0f) } + var isDragging by remember { mutableStateOf(false) } + var dragThumbOffset by remember { mutableFloatStateOf(0f) } + + val thumbHeightDp = 48.dp + val thumbHeight = with(density) { thumbHeightDp.toPx() } + + // Show scrollbar only when there are enough items + val showScrollbar by remember(totalItems, columns) { + derivedStateOf { totalItems > columns * 3 } + } + + // Calculate scroll progress (0 to 1) based on grid state + val scrollProgress by remember(lazyGridState) { + derivedStateOf { + if (totalItems == 0 || columns == 0) return@derivedStateOf 0f + + val totalRows = (totalItems + columns - 1) / columns + val firstVisibleRow = lazyGridState.firstVisibleItemIndex / columns + + if (totalRows <= 1) 0f + else (firstVisibleRow.toFloat() / (totalRows - 1).coerceAtLeast(1)) + .coerceIn(0f, 1f) + } + } + + AnimatedVisibility( + visible = showScrollbar, + enter = fadeIn(), + exit = fadeOut(), + modifier = modifier, + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .width(24.dp) + .padding(vertical = 8.dp, horizontal = 4.dp) + .onSizeChanged { containerHeight = it.height.toFloat() } + .pointerInput(totalItems, columns) { + detectVerticalDragGestures( + onDragStart = { offset -> + isDragging = true + // Initialize drag offset from current scroll position + val maxThumbOffset = (containerHeight - thumbHeight).coerceAtLeast(0f) + dragThumbOffset = scrollProgress * maxThumbOffset + }, + onDragEnd = { isDragging = false }, + onDragCancel = { isDragging = false }, + onVerticalDrag = { change, dragAmount -> + change.consume() + + val maxThumbOffset = (containerHeight - thumbHeight).coerceAtLeast(0f) + + // Update drag offset directly + dragThumbOffset = (dragThumbOffset + dragAmount) + .coerceIn(0f, maxThumbOffset) + + val newProgress = if (maxThumbOffset > 0) { + dragThumbOffset / maxThumbOffset + } else 0f + + val totalRows = (totalItems + columns - 1) / columns + val targetRow = (newProgress * (totalRows - 1)).roundToInt() + val targetIndex = (targetRow * columns).coerceIn(0, totalItems - 1) + + coroutineScope.launch { + lazyGridState.scrollToItem(targetIndex) + } + } + ) + }, + contentAlignment = Alignment.TopCenter, + ) { + // Track background + Box( + modifier = Modifier + .fillMaxHeight() + .width(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background( + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) + ) + ) + + // Calculate thumb offset - use drag offset when dragging, scroll progress otherwise + val maxThumbOffset = (containerHeight - thumbHeight).coerceAtLeast(0f) + val thumbOffset = if (isDragging) { + dragThumbOffset.roundToInt() + } else { + (scrollProgress * maxThumbOffset).roundToInt() + } + + // Thumb + Box( + modifier = Modifier + .offset { IntOffset(0, thumbOffset) } + .width(16.dp) + .height(thumbHeightDp) + .clip(RoundedCornerShape(8.dp)) + .background( + if (isDragging) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.primary.copy(alpha = 0.7f) + } + ) + ) + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GalleryItemStateEvent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GalleryItemStateEvent.kt new file mode 100644 index 000000000..64c482098 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GalleryItemStateEvent.kt @@ -0,0 +1,29 @@ +package dev.minios.pdaiv1.presentation.core + +import io.reactivex.rxjava3.core.BackpressureStrategy +import io.reactivex.rxjava3.subjects.PublishSubject + +/** + * Event bus for synchronizing gallery item states (hidden, liked) between + * GalleryDetailViewModel and GalleryViewModel in real-time. + */ +class GalleryItemStateEvent { + + private val hiddenSubject: PublishSubject = PublishSubject.create() + private val likedSubject: PublishSubject = PublishSubject.create() + + fun emitHiddenChange(itemId: Long, hidden: Boolean) { + hiddenSubject.onNext(HiddenChange(itemId, hidden)) + } + + fun emitLikedChange(itemId: Long, liked: Boolean) { + likedSubject.onNext(LikedChange(itemId, liked)) + } + + fun observeHiddenChanges() = hiddenSubject.toFlowable(BackpressureStrategy.BUFFER) + + fun observeLikedChanges() = likedSubject.toFlowable(BackpressureStrategy.BUFFER) + + data class HiddenChange(val itemId: Long, val hidden: Boolean) + data class LikedChange(val itemId: Long, val liked: Boolean) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationFormUpdateEvent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationFormUpdateEvent.kt new file mode 100644 index 000000000..3970d6556 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationFormUpdateEvent.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.presentation.core + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import io.reactivex.rxjava3.core.BackpressureStrategy +import io.reactivex.rxjava3.subjects.BehaviorSubject +import io.reactivex.rxjava3.subjects.PublishSubject + +class GenerationFormUpdateEvent { + + private val sRoute: PublishSubject = PublishSubject.create() + private val sFalAiRoute: PublishSubject = PublishSubject.create() + private val sTxt2Img: BehaviorSubject = BehaviorSubject.createDefault(Payload.None) + private val sImg2Img: BehaviorSubject = BehaviorSubject.createDefault(Payload.None) + private val sFalAi: BehaviorSubject = BehaviorSubject.createDefault(Payload.None) + + fun update( + generation: AiGenerationResult, + route: AiGenerationResult.Type, + inputImage: Boolean, + ) { + sRoute.onNext(route) + when (route) { + AiGenerationResult.Type.TEXT_TO_IMAGE -> sTxt2Img.onNext(Payload.T2IForm(generation)) + AiGenerationResult.Type.IMAGE_TO_IMAGE -> sImg2Img.onNext(Payload.I2IForm(generation, inputImage)) + } + } + + fun clear() { + sTxt2Img.onNext(Payload.None) + sImg2Img.onNext(Payload.None) + sFalAi.onNext(Payload.None) + } + + fun observeRoute() = sRoute.toFlowable(BackpressureStrategy.LATEST) + + fun observeTxt2ImgForm() = sTxt2Img.toFlowable(BackpressureStrategy.LATEST) + + fun observeImg2ImgForm() = sImg2Img.toFlowable(BackpressureStrategy.LATEST) + + fun updateFalAi(generation: AiGenerationResult) { + sFalAiRoute.onNext(Unit) + sFalAi.onNext(Payload.FalAiForm(generation)) + } + + fun observeFalAiRoute() = sFalAiRoute.toFlowable(BackpressureStrategy.LATEST) + + fun observeFalAiForm() = sFalAi.toFlowable(BackpressureStrategy.LATEST) + + sealed interface Payload { + data object None : Payload + data class T2IForm(val ai: AiGenerationResult): Payload + data class I2IForm(val ai: AiGenerationResult, val inputImage: Boolean): Payload + data class FalAiForm(val ai: AiGenerationResult): Payload + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviIntent.kt new file mode 100644 index 000000000..6248cb023 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviIntent.kt @@ -0,0 +1,149 @@ +package dev.minios.pdaiv1.presentation.core + +import android.graphics.Bitmap +import dev.minios.pdaiv1.domain.entity.ADetailerConfig +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.domain.entity.HiresConfig +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.OpenAiQuality +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.OpenAiStyle +import dev.minios.pdaiv1.domain.entity.QnnHiresConfig +import dev.minios.pdaiv1.domain.entity.StabilityAiClipGuidance +import dev.minios.pdaiv1.domain.entity.StabilityAiStylePreset +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.model.QnnResolution +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface GenerationMviIntent : MviIntent { + + data class NewPrompts( + val positive: String, + val negative: String, + ) : GenerationMviIntent + + data class SetAdvancedOptionsVisibility(val visible: Boolean) : GenerationMviIntent + + sealed interface Update : GenerationMviIntent { + + data class Prompt(val value: String) : Update + + data class NegativePrompt(val value: String) : Update + + sealed interface Size : Update { + + data class Width(val value: String) : Size + + data class Height(val value: String) : Size + + data object Swap : Size + + data class AspectRatio(val ratio: dev.minios.pdaiv1.presentation.model.AspectRatio) : Size + } + + data class SamplingSteps(val value: Int) : Update + + data class CfgScale(val value: Float) : Update + + data class DistilledCfgScale(val value: Float) : Update + + data class ModelTypeChange(val value: ModelType) : Update + + data class RestoreFaces(val value: Boolean) : Update + + data class Seed(val value: String) : Update + + data class SubSeed(val value: String) : Update + + data class SubSeedStrength(val value: Float) : Update + + data class Sampler(val value: String) : Update + + data class Nsfw(val value: Boolean) : Update + + data class Batch(val value: Int) : Update + + data class Scheduler(val value: dev.minios.pdaiv1.domain.entity.Scheduler) : Update + + data class ADetailer(val value: ADetailerConfig) : Update + + data class Hires(val value: HiresConfig) : Update + + data class ForgeModules(val value: List) : Update + + sealed interface OpenAi : Update { + + data class Model(val value: OpenAiModel) : OpenAi + + data class Size(val value: OpenAiSize) : OpenAi + + data class Quality(val value: OpenAiQuality) : OpenAi + + data class Style(val value: OpenAiStyle) : OpenAi + } + + sealed interface StabilityAi : Update { + data class Style(val value: StabilityAiStylePreset) : StabilityAi + + data class ClipGuidance(val value: StabilityAiClipGuidance) : StabilityAi + } + + sealed interface FalAi : Update { + data class SelectEndpoint(val endpointId: String) : FalAi + data class UpdateProperty(val name: String, val value: Any?) : FalAi + data class ToggleAdvanced(val visible: Boolean) : FalAi + } + + sealed interface Qnn : Update { + data class Resolution(val value: QnnResolution) : Qnn + data class Hires(val value: QnnHiresConfig) : Qnn + } + } + + sealed interface Result : GenerationMviIntent { + + data class Save(val ai: List) : Result + + data class View(val ai: AiGenerationResult) : Result + + data class Report(val ai: AiGenerationResult) : Result + } + + data class SetModal(val modal: Modal) : GenerationMviIntent + + enum class Cancel : GenerationMviIntent { + Generation, FetchRandomImage, + } + + data object Configuration : GenerationMviIntent + + data object Generate : GenerationMviIntent + + data class UpdateFromGeneration( + val payload: GenerationFormUpdateEvent.Payload, + ) : GenerationMviIntent + + data class Drawer(val intent: DrawerIntent) : GenerationMviIntent +} + +sealed interface ImageToImageIntent : GenerationMviIntent { + + data object InPaint : ImageToImageIntent + + data object FetchRandomPhoto : ImageToImageIntent + + data object ClearImageInput : ImageToImageIntent + + data class UpdateDenoisingStrength(val value: Float) : ImageToImageIntent + + data class UpdateImage(val bitmap: Bitmap) : ImageToImageIntent + + data class CropImage(val bitmap: Bitmap) : ImageToImageIntent + + enum class Pick : ImageToImageIntent { + Camera, Gallery + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviState.kt new file mode 100644 index 000000000..b57c97522 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviState.kt @@ -0,0 +1,130 @@ +package dev.minios.pdaiv1.presentation.core + +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.domain.entity.ADetailerConfig +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.domain.entity.HiresConfig +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.QnnHiresConfig +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.OpenAiQuality +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.OpenAiStyle +import dev.minios.pdaiv1.domain.entity.Scheduler +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.StabilityAiClipGuidance +import dev.minios.pdaiv1.domain.entity.StabilityAiStylePreset +import dev.minios.pdaiv1.presentation.model.FalAiEndpointUi +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviState + +abstract class GenerationMviState : MviState { + abstract val onBoardingDemo: Boolean + abstract val screenModal: Modal + abstract val mode: ServerSource + abstract val modelType: ModelType + abstract val advancedToggleButtonVisible: Boolean + abstract val advancedOptionsVisible: Boolean + abstract val formPromptTaggedInput: Boolean + abstract val prompt: String + abstract val negativePrompt: String + abstract val width: String + abstract val height: String + abstract val samplingSteps: Int + abstract val cfgScale: Float + abstract val distilledCfgScale: Float + abstract val restoreFaces: Boolean + abstract val seed: String + abstract val subSeed: String + abstract val subSeedStrength: Float + abstract val selectedSampler: String + abstract val availableSamplers: List + abstract val selectedScheduler: Scheduler + abstract val availableForgeModules: List + abstract val selectedForgeModules: List + abstract val aDetailerConfig: ADetailerConfig + abstract val hiresConfig: HiresConfig + abstract val selectedStylePreset: StabilityAiStylePreset + abstract val selectedClipGuidancePreset: StabilityAiClipGuidance + abstract val openAiModel: OpenAiModel + abstract val openAiSize: OpenAiSize + abstract val openAiQuality: OpenAiQuality + abstract val openAiStyle: OpenAiStyle + abstract val widthValidationError: UiText? + abstract val heightValidationError: UiText? + abstract val nsfw: Boolean + abstract val batchCount: Int + abstract val generateButtonEnabled: Boolean + + // FalAi specific fields + abstract val falAiEndpoints: List + abstract val falAiSelectedEndpoint: FalAiEndpointUi? + abstract val falAiPropertyValues: Map + abstract val falAiAdvancedVisible: Boolean + + // QNN specific fields + abstract val qnnRunOnCpu: Boolean + abstract val qnnHiresConfig: QnnHiresConfig + + // Model name for saving with generation result + abstract val modelName: String + + open val promptKeywords: List + get() = prompt.split(",") + .map { it.trim() } + .filter { it.isNotEmpty() } + + open val negativePromptKeywords: List + get() = negativePrompt.split(",") + .map { it.trim() } + .filter { it.isNotEmpty() } + + open val hasValidationErrors: Boolean + get() = widthValidationError != null || heightValidationError != null + + open fun copyState( + onBoardingDemo: Boolean = this.onBoardingDemo, + screenModal: Modal = this.screenModal, + mode: ServerSource = this.mode, + modelType: ModelType = this.modelType, + advancedToggleButtonVisible: Boolean = this.advancedToggleButtonVisible, + advancedOptionsVisible: Boolean = this.advancedOptionsVisible, + formPromptTaggedInput: Boolean = this.formPromptTaggedInput, + prompt: String = this.prompt, + negativePrompt: String = this.negativePrompt, + width: String = this.width, + height: String = this.height, + samplingSteps: Int = this.samplingSteps, + cfgScale: Float = this.cfgScale, + distilledCfgScale: Float = this.distilledCfgScale, + restoreFaces: Boolean = this.restoreFaces, + seed: String = this.seed, + subSeed: String = this.subSeed, + subSeedStrength: Float = this.subSeedStrength, + selectedSampler: String = this.selectedSampler, + availableSamplers: List = this.availableSamplers, + selectedScheduler: Scheduler = this.selectedScheduler, + availableForgeModules: List = this.availableForgeModules, + selectedForgeModules: List = this.selectedForgeModules, + aDetailerConfig: ADetailerConfig = this.aDetailerConfig, + hiresConfig: HiresConfig = this.hiresConfig, + selectedStylePreset: StabilityAiStylePreset = this.selectedStylePreset, + selectedClipGuidancePreset: StabilityAiClipGuidance = this.selectedClipGuidancePreset, + openAiModel: OpenAiModel = this.openAiModel, + openAiSize: OpenAiSize = this.openAiSize, + openAiQuality: OpenAiQuality = this.openAiQuality, + openAiStyle: OpenAiStyle = this.openAiStyle, + widthValidationError: UiText? = this.widthValidationError, + heightValidationError: UiText? = this.heightValidationError, + nsfw: Boolean = this.nsfw, + batchCount: Int = this.batchCount, + generateButtonEnabled: Boolean = this.generateButtonEnabled, + falAiEndpoints: List = this.falAiEndpoints, + falAiSelectedEndpoint: FalAiEndpointUi? = this.falAiSelectedEndpoint, + falAiPropertyValues: Map = this.falAiPropertyValues, + falAiAdvancedVisible: Boolean = this.falAiAdvancedVisible, + qnnRunOnCpu: Boolean = this.qnnRunOnCpu, + qnnHiresConfig: QnnHiresConfig = this.qnnHiresConfig, + modelName: String = this.modelName, + ): GenerationMviState = this +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviViewModel.kt new file mode 100644 index 000000000..ef2235be8 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/core/GenerationMviViewModel.kt @@ -0,0 +1,512 @@ +@file:Suppress("UNCHECKED_CAST") + +package dev.minios.pdaiv1.presentation.core + +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.ModelType.Companion.defaultCfgScale +import dev.minios.pdaiv1.domain.entity.ModelType.Companion.defaultHeight +import dev.minios.pdaiv1.domain.entity.ModelType.Companion.defaultSampler +import dev.minios.pdaiv1.domain.entity.ModelType.Companion.defaultScheduler +import dev.minios.pdaiv1.domain.entity.ModelType.Companion.defaultWidth +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.StabilityAiSampler +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.caching.SaveLastResultToCacheUseCase +import dev.minios.pdaiv1.domain.usecase.forgemodule.GetForgeModulesUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.SaveGenerationResultUseCase +import dev.minios.pdaiv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.screen.txt2img.mapToUi +import com.shifthackz.android.core.mvi.MviEffect +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.kotlin.subscribeBy +import java.util.concurrent.TimeUnit + +abstract class GenerationMviViewModel( + private val preferenceManager: PreferenceManager, + getStableDiffusionSamplersUseCase: GetStableDiffusionSamplersUseCase, + getForgeModulesUseCase: GetForgeModulesUseCase, + observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + private val saveLastResultToCacheUseCase: SaveLastResultToCacheUseCase, + private val saveGenerationResultUseCase: SaveGenerationResultUseCase, + private val interruptGenerationUseCase: InterruptGenerationUseCase, + private val mainRouter: MainRouter, + private val drawerRouter: DrawerRouter, + private val dimensionValidator: DimensionValidator, + private val schedulersProvider: SchedulersProvider, + private val backgroundWorkObserver: BackgroundWorkObserver, +) : MviRxViewModel() { + + private var generationDisposable: Disposable? = null + private var randomImageDisposable: Disposable? = null + + init { + !preferenceManager + .observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onComplete = EmptyLambda, + onNext = { settings -> + updateGenerationState { + val modelTypeChanged = it.modelType != settings.modelType + val sourceChanged = it.mode != settings.source + it + .copyState( + mode = settings.source, + modelType = settings.modelType, + advancedToggleButtonVisible = !settings.formAdvancedOptionsAlwaysShow, + formPromptTaggedInput = settings.formPromptTaggedInput, + ) + .let { state -> + // When switching to QNN, set default resolution and samplers + if (sourceChanged && settings.source == ServerSource.LOCAL_QUALCOMM_QNN) { + val qnnSamplers = listOf("DPM++ 2M", "Euler a") + val runOnCpu = preferenceManager.localQnnRunOnCpu + val defaultRes = if (runOnCpu) "256" else "512" + state.copyState( + width = defaultRes, + height = defaultRes, + widthValidationError = null, + heightValidationError = null, + availableSamplers = qnnSamplers, + selectedSampler = qnnSamplers.first(), + prompt = preferenceManager.localQnnLastPrompt, + negativePrompt = preferenceManager.localQnnLastNegativePrompt, + qnnRunOnCpu = runOnCpu, + ) + } else if (sourceChanged && settings.source == ServerSource.LOCAL_MICROSOFT_ONNX) { + // When switching to ONNX, restore last prompt + state.copyState( + prompt = preferenceManager.localOnnxLastPrompt, + negativePrompt = preferenceManager.localOnnxLastNegativePrompt, + ) + } else if (sourceChanged && settings.source == ServerSource.LOCAL_GOOGLE_MEDIA_PIPE) { + // When switching to MediaPipe, restore last prompt + state.copyState( + prompt = preferenceManager.localMediaPipeLastPrompt, + negativePrompt = preferenceManager.localMediaPipeLastNegativePrompt, + ) + } else if (settings.source == ServerSource.LOCAL_QUALCOMM_QNN) { + // Already in QNN mode, just update runOnCpu if changed + val runOnCpu = preferenceManager.localQnnRunOnCpu + if (state.qnnRunOnCpu != runOnCpu) { + val defaultRes = if (runOnCpu) "256" else "512" + state.copyState( + qnnRunOnCpu = runOnCpu, + width = defaultRes, + height = defaultRes, + ) + } else state + } else if (modelTypeChanged) { + state.copyState( + cfgScale = settings.modelType.defaultCfgScale(), + selectedSampler = settings.modelType.defaultSampler(), + selectedScheduler = settings.modelType.defaultScheduler(), + width = settings.modelType.defaultWidth().toString(), + height = settings.modelType.defaultHeight().toString(), + ) + } else state + } + .let { state -> + if (!settings.formAdvancedOptionsAlwaysShow) state + else state.copyState(advancedOptionsVisible = true) + } + .let { state -> + state.copyState(modelName = getModelName(settings.source)) + } + } + } + ) + + !getStableDiffusionSamplersUseCase() + .map { samplers -> samplers.map(StableDiffusionSampler::name) } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onSuccess = { samplers -> + updateGenerationState { state -> + val allSamplers = when (state.mode) { + ServerSource.STABILITY_AI -> StabilityAiSampler.entries.map { "$it" } + ServerSource.LOCAL_QUALCOMM_QNN -> listOf("DPM++ 2M", "Euler a") + else -> samplers + } + state.copyState( + availableSamplers = allSamplers, + selectedSampler = allSamplers.firstOrNull() ?: "", + ) + } + } + ) + + !observeHordeProcessStatusUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onNext = ::onReceivedHordeStatus, + onComplete = EmptyLambda, + ) + + !observeLocalDiffusionProcessStatusUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onNext = ::onReceivedLocalDiffusionStatus, + onComplete = EmptyLambda, + ) + + !getForgeModulesUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onSuccess = { modules -> + updateGenerationState { state -> + state.copyState(availableForgeModules = modules) + } + } + ) + } + + abstract fun generateDisposable(): Disposable + + abstract fun generateBackground() + + open fun onReceivedHordeStatus(status: HordeProcessStatus) {} + + open fun onReceivedLocalDiffusionStatus(status: LocalDiffusionStatus) {} + + override fun onCleared() { + super.onCleared() + generationDisposable?.dispose() + generationDisposable = null + randomImageDisposable?.dispose() + randomImageDisposable = null + } + + override fun processIntent(intent: I) { + when (intent) { + is GenerationMviIntent.NewPrompts -> updateGenerationState { + it.copyState( + prompt = intent.positive.trim(), + negativePrompt = intent.negative.trim(), + ) + } + + is GenerationMviIntent.SetAdvancedOptionsVisibility -> updateGenerationState { + it.copyState(advancedOptionsVisible = intent.visible) + } + + is GenerationMviIntent.Update.Prompt -> updateGenerationState { + it.copyState(prompt = intent.value) + } + + is GenerationMviIntent.Update.NegativePrompt -> updateGenerationState { + it.copyState(negativePrompt = intent.value) + } + + is GenerationMviIntent.Update.Size.Width -> updateGenerationState { + it.copyState( + width = intent.value, + widthValidationError = dimensionValidator(intent.value).mapToUi(), + ) + } + + is GenerationMviIntent.Update.Size.Height -> updateGenerationState { + it.copyState( + height = intent.value, + heightValidationError = dimensionValidator(intent.value).mapToUi(), + ) + } + + is GenerationMviIntent.Update.Size.Swap -> updateGenerationState { + val newWidth = it.height + val newHeight = it.width + it.copyState( + width = newWidth, + height = newHeight, + widthValidationError = dimensionValidator(newWidth).mapToUi(), + heightValidationError = dimensionValidator(newHeight).mapToUi(), + ) + } + + is GenerationMviIntent.Update.Size.AspectRatio -> updateGenerationState { + val baseSize = it.width.toIntOrNull() ?: 512 + val (newWidth, newHeight) = intent.ratio.calculateDimensions(baseSize) + it.copyState( + width = newWidth.toString(), + height = newHeight.toString(), + widthValidationError = dimensionValidator(newWidth.toString()).mapToUi(), + heightValidationError = dimensionValidator(newHeight.toString()).mapToUi(), + ) + } + + is GenerationMviIntent.Update.SamplingSteps -> updateGenerationState { + it.copyState(samplingSteps = intent.value) + } + + is GenerationMviIntent.Update.CfgScale -> updateGenerationState { + it.copyState(cfgScale = intent.value) + } + + is GenerationMviIntent.Update.RestoreFaces -> updateGenerationState { + it.copyState(restoreFaces = intent.value) + } + + is GenerationMviIntent.Update.Seed -> updateGenerationState { + it.copyState(seed = intent.value) + } + + is GenerationMviIntent.Update.SubSeed -> updateGenerationState { + it.copyState(subSeed = intent.value) + } + + is GenerationMviIntent.Update.SubSeedStrength -> updateGenerationState { + it.copyState(subSeedStrength = intent.value) + } + + is GenerationMviIntent.Update.Sampler -> updateGenerationState { + it.copyState(selectedSampler = intent.value) + } + + is GenerationMviIntent.Update.Nsfw -> updateGenerationState { + it.copyState(nsfw = intent.value) + } + + is GenerationMviIntent.Update.Batch -> updateGenerationState { + it.copyState(batchCount = intent.value) + } + + is GenerationMviIntent.Update.Scheduler -> updateGenerationState { + it.copyState(selectedScheduler = intent.value) + } + + is GenerationMviIntent.Update.ADetailer -> updateGenerationState { + it.copyState(aDetailerConfig = intent.value) + } + + is GenerationMviIntent.Update.Hires -> updateGenerationState { + it.copyState(hiresConfig = intent.value) + } + + is GenerationMviIntent.Update.ForgeModules -> updateGenerationState { + it.copyState(selectedForgeModules = intent.value) + } + + is GenerationMviIntent.Update.DistilledCfgScale -> updateGenerationState { + it.copyState(distilledCfgScale = intent.value) + } + + is GenerationMviIntent.Update.ModelTypeChange -> { + preferenceManager.modelType = intent.value + updateGenerationState { + it.copyState( + modelType = intent.value, + cfgScale = intent.value.defaultCfgScale(), + selectedSampler = intent.value.defaultSampler(), + selectedScheduler = intent.value.defaultScheduler(), + width = intent.value.defaultWidth().toString(), + height = intent.value.defaultHeight().toString(), + ) + } + } + + is GenerationMviIntent.Update.OpenAi.Model -> updateGenerationState { state -> + val size = if (state.openAiSize.supportedModels.contains(intent.value)) { + state.openAiSize + } else { + OpenAiSize.entries.first { it.supportedModels.contains(intent.value) } + } + state.copyState(openAiModel = intent.value, openAiSize = size) + } + + is GenerationMviIntent.Update.OpenAi.Size -> updateGenerationState { + it.copyState(openAiSize = intent.value) + } + + is GenerationMviIntent.Update.OpenAi.Quality -> updateGenerationState { + it.copyState(openAiQuality = intent.value) + } + + is GenerationMviIntent.Update.OpenAi.Style -> updateGenerationState { + it.copyState(openAiStyle = intent.value) + } + + is GenerationMviIntent.Update.Qnn.Resolution -> updateGenerationState { + it.copyState( + width = intent.value.width.toString(), + height = intent.value.height.toString(), + widthValidationError = null, + heightValidationError = null, + ) + } + + is GenerationMviIntent.Update.Qnn.Hires -> updateGenerationState { + it.copyState(qnnHiresConfig = intent.value) + } + + is GenerationMviIntent.Result.Save -> !Observable + .fromIterable(intent.ai) + .flatMapCompletable(saveGenerationResultUseCase::invoke) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { setActiveModal(Modal.None) } + + is GenerationMviIntent.Result.View -> !saveLastResultToCacheUseCase(intent.ai) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { mainRouter.navigateToGalleryDetails(it.id) } + + is GenerationMviIntent.Result.Report -> mainRouter.navigateToReportImage(intent.ai.id) + + is GenerationMviIntent.SetModal -> setActiveModal(intent.modal) + + GenerationMviIntent.Cancel.Generation -> { + generationDisposable?.dispose() + generationDisposable = null + !interruptGenerationUseCase() + .doOnSubscribe { setActiveModal(Modal.None) } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) + } + + GenerationMviIntent.Cancel.FetchRandomImage -> { + randomImageDisposable?.dispose() + randomImageDisposable = null + setActiveModal(Modal.None) + } + + GenerationMviIntent.Generate -> { + // Auto-save prompts for local backends + val state = currentState as? GenerationMviState + state?.let { + when (preferenceManager.source) { + ServerSource.LOCAL_QUALCOMM_QNN -> { + preferenceManager.localQnnLastPrompt = it.prompt.trim() + preferenceManager.localQnnLastNegativePrompt = it.negativePrompt.trim() + } + ServerSource.LOCAL_MICROSOFT_ONNX -> { + preferenceManager.localOnnxLastPrompt = it.prompt.trim() + preferenceManager.localOnnxLastNegativePrompt = it.negativePrompt.trim() + } + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> { + preferenceManager.localMediaPipeLastPrompt = it.prompt.trim() + preferenceManager.localMediaPipeLastNegativePrompt = it.negativePrompt.trim() + } + else -> Unit + } + } + + if (backgroundWorkObserver.hasActiveTasks()) { + setActiveModal(Modal.Background.Running) + } else { + if (preferenceManager.backgroundGeneration) { + generateBackground() + backgroundWorkObserver.refreshStatus() + setActiveModal(Modal.Background.Scheduled) + } else { + generateOnUi { generateDisposable() } + } + } + } + + GenerationMviIntent.Configuration -> mainRouter.navigateToServerSetup( + LaunchSource.SETTINGS, + ) + + is GenerationMviIntent.UpdateFromGeneration -> { + updateFormPreviousAiGeneration(intent.payload) + } + + is GenerationMviIntent.Drawer -> when (intent.intent) { + DrawerIntent.Close -> drawerRouter.closeDrawer() + DrawerIntent.Open -> drawerRouter.openDrawer() + } + + else -> Unit + } + } + + protected open fun updateFormPreviousAiGeneration(payload: GenerationFormUpdateEvent.Payload) { + val ai = when (payload) { + is GenerationFormUpdateEvent.Payload.I2IForm -> payload.ai + is GenerationFormUpdateEvent.Payload.T2IForm -> payload.ai + else -> return + } + updateGenerationState { oldState -> + oldState + .copyState( + advancedOptionsVisible = true, + prompt = ai.prompt, + negativePrompt = ai.negativePrompt, + width = "${ai.width}", + height = "${ai.height}", + seed = ai.seed, + subSeed = ai.subSeed, + subSeedStrength = ai.subSeedStrength, + samplingSteps = ai.samplingSteps, + cfgScale = ai.cfgScale, + restoreFaces = ai.restoreFaces, + ) + .let { state -> + if (!state.availableSamplers.contains(ai.sampler)) state + else state.copyState(selectedSampler = ai.sampler) + } + } + } + + protected fun setActiveModal(modal: Modal) = updateGenerationState { + it.copyState(screenModal = modal) + } + + protected fun fetchRandomImage(fn: () -> Disposable) { + randomImageDisposable?.dispose() + randomImageDisposable = null + val newDisposable = fn() + randomImageDisposable = newDisposable + randomImageDisposable?.addToDisposable() + } + + private fun generateOnUi(fn: () -> Disposable) { + generationDisposable?.dispose() + generationDisposable = null + val newDisposable = fn() + generationDisposable = newDisposable + generationDisposable?.addToDisposable() + } + + private fun updateGenerationState(mutation: (GenerationMviState) -> GenerationMviState) = + runCatching { + updateState(mutation as (S) -> S) + } + + private fun getModelName(source: ServerSource): String = when (source) { + ServerSource.AUTOMATIC1111 -> preferenceManager.sdModel + ServerSource.SWARM_UI -> preferenceManager.swarmUiModel + ServerSource.LOCAL_MICROSOFT_ONNX -> preferenceManager.localOnnxModelId + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> preferenceManager.localMediaPipeModelId + ServerSource.LOCAL_QUALCOMM_QNN -> preferenceManager.localQnnModelId + ServerSource.HORDE -> "Horde" + ServerSource.HUGGING_FACE -> preferenceManager.huggingFaceModel + ServerSource.OPEN_AI -> "OpenAI" + ServerSource.STABILITY_AI -> preferenceManager.stabilityAiEngineId + ServerSource.FAL_AI -> preferenceManager.falAiSelectedEndpointId + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/NavigationModule.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/NavigationModule.kt new file mode 100644 index 000000000..a3e887981 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/NavigationModule.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.presentation.di + +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouterImpl +import dev.minios.pdaiv1.presentation.navigation.router.home.HomeRouter +import dev.minios.pdaiv1.presentation.navigation.router.home.HomeRouterImpl +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouterImpl +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +internal val navigationModule = module { + singleOf(::MainRouterImpl) bind MainRouter::class + singleOf(::DrawerRouterImpl) bind DrawerRouter::class + singleOf(::HomeRouterImpl) bind HomeRouter::class +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/PresentationModule.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/PresentationModule.kt new file mode 100644 index 000000000..abbf20b2a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/PresentationModule.kt @@ -0,0 +1,3 @@ +package dev.minios.pdaiv1.presentation.di + +val presentationModule = (navigationModule + viewModelModule + uiUtilsModule).toTypedArray() diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/UiUtilsModule.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/UiUtilsModule.kt new file mode 100644 index 000000000..fdbfad827 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/UiUtilsModule.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.presentation.di + +import dev.minios.pdaiv1.presentation.core.GalleryItemStateEvent +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.screen.debug.DebugMenuAccessor +import dev.minios.pdaiv1.presentation.screen.gallery.detail.GalleryDetailBitmapExporter +import dev.minios.pdaiv1.presentation.screen.gallery.detail.GalleryDetailSharing +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryExporter +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintStateProducer +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal val uiUtilsModule = module { + factoryOf(::GalleryExporter) + factoryOf(::GalleryDetailBitmapExporter) + factoryOf(::GalleryDetailSharing) + singleOf(::GenerationFormUpdateEvent) + singleOf(::GalleryItemStateEvent) + singleOf(::DebugMenuAccessor) + singleOf(::InPaintStateProducer) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/ViewModelModule.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/ViewModelModule.kt new file mode 100755 index 000000000..629087f4e --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/di/ViewModelModule.kt @@ -0,0 +1,206 @@ +package dev.minios.pdaiv1.presentation.di + +import dev.minios.pdaiv1.presentation.activity.AiStableDiffusionViewModel +import dev.minios.pdaiv1.presentation.modal.download.DownloadDialogViewModel +import dev.minios.pdaiv1.presentation.modal.embedding.EmbeddingViewModel +import dev.minios.pdaiv1.presentation.modal.extras.ExtrasViewModel +import dev.minios.pdaiv1.presentation.modal.history.InputHistoryViewModel +import dev.minios.pdaiv1.presentation.modal.tag.EditTagViewModel +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.screen.debug.DebugMenuViewModel +import dev.minios.pdaiv1.presentation.screen.donate.DonateViewModel +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerViewModel +import dev.minios.pdaiv1.presentation.screen.falai.FalAiGenerationViewModel +import dev.minios.pdaiv1.presentation.screen.gallery.detail.GalleryDetailViewModel +import dev.minios.pdaiv1.presentation.screen.gallery.editor.ImageEditorViewModel +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryViewModel +import dev.minios.pdaiv1.presentation.screen.home.HomeNavigationViewModel +import dev.minios.pdaiv1.presentation.screen.img2img.ImageToImageViewModel +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintViewModel +import dev.minios.pdaiv1.presentation.screen.loader.ConfigurationLoaderViewModel +import dev.minios.pdaiv1.presentation.screen.logger.LoggerViewModel +import dev.minios.pdaiv1.presentation.screen.onboarding.OnBoardingViewModel +import dev.minios.pdaiv1.presentation.screen.report.ReportViewModel +import dev.minios.pdaiv1.presentation.screen.settings.SettingsViewModel +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupViewModel +import dev.minios.pdaiv1.presentation.screen.splash.SplashViewModel +import dev.minios.pdaiv1.presentation.screen.txt2img.TextToImageViewModel +import dev.minios.pdaiv1.presentation.screen.web.webui.WebUiViewModel +import dev.minios.pdaiv1.presentation.theme.global.AiSdAppThemeViewModel +import dev.minios.pdaiv1.presentation.widget.connectivity.ConnectivityViewModel +import dev.minios.pdaiv1.presentation.widget.engine.EngineSelectionViewModel +import dev.minios.pdaiv1.presentation.widget.work.BackgroundWorkViewModel +import org.koin.core.module.dsl.viewModel +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module + +val viewModelModule = module { + viewModelOf(::AiStableDiffusionViewModel) + viewModelOf(::AiSdAppThemeViewModel) + viewModelOf(::SplashViewModel) + viewModelOf(::DrawerViewModel) + viewModelOf(::HomeNavigationViewModel) + viewModelOf(::ConfigurationLoaderViewModel) + viewModelOf(::TextToImageViewModel) + viewModelOf(::SettingsViewModel) + viewModel { + GalleryViewModel( + dispatchersProvider = get(), + getMediaStoreInfoUseCase = get(), + backgroundWorkObserver = get(), + preferenceManager = get(), + deleteAllGalleryUseCase = get(), + deleteAllUnlikedUseCase = get(), + deleteGalleryItemsUseCase = get(), + getGenerationResultPagedUseCase = get(), + getGalleryPagedIdsUseCase = get(), + getGalleryItemsUseCase = get(), + getGalleryItemsRawUseCase = get(), + getThumbnailInfoUseCase = get(), + base64ToBitmapConverter = get(), + thumbnailGenerator = get(), + galleryExporter = get(), + schedulersProvider = get(), + mainRouter = get(), + drawerRouter = get(), + mediaStoreGateway = get(), + mediaFileManager = get(), + getAllGalleryUseCase = get(), + galleryItemStateEvent = get(), + likeItemsUseCase = get(), + unlikeItemsUseCase = get(), + hideItemsUseCase = get(), + unhideItemsUseCase = get(), + ) + } + viewModelOf(::ConnectivityViewModel) + viewModelOf(::InputHistoryViewModel) + viewModelOf(::DebugMenuViewModel) + viewModelOf(::ExtrasViewModel) + viewModelOf(::EmbeddingViewModel) + viewModelOf(::EditTagViewModel) + viewModelOf(::InPaintViewModel) + viewModelOf(::EngineSelectionViewModel) + viewModelOf(::WebUiViewModel) + viewModelOf(::DonateViewModel) + viewModelOf(::BackgroundWorkViewModel) + viewModelOf(::LoggerViewModel) + viewModelOf(::DownloadDialogViewModel) + viewModelOf(::FalAiGenerationViewModel) + + viewModel { parameters -> + OnBoardingViewModel( + launchSource = LaunchSource.fromKey(parameters.get()), + dispatchersProvider = get(), + mainRouter = get(), + splashNavigationUseCase = get(), + preferenceManager = get(), + schedulersProvider = get(), + buildInfoProvider = get(), + ) + } + + viewModel { parameters -> + val launchSource = LaunchSource.fromKey(parameters.get()) + ServerSetupViewModel( + launchSource = launchSource, + dispatchersProvider = get(), + getConfigurationUseCase = get(), + getLocalOnnxModelsUseCase = get(), + getLocalMediaPipeModelsUseCase = get(), + getLocalQnnModelsUseCase = get(), + fetchAndGetHuggingFaceModelsUseCase = get(), + falAiEndpointRepository = get(), + urlValidator = get(), + stringValidator = get(), + filePathValidator = get(), + setupConnectionInterActor = get(), + downloadModelUseCase = get(), + deleteModelUseCase = get(), + scanCustomModelsUseCase = get(), + schedulersProvider = get(), + preferenceManager = get(), + wakeLockInterActor = get(), + mainRouter = get(), + buildInfoProvider = get(), + ) + } + + viewModel { parameters -> + GalleryDetailViewModel( + itemId = parameters.get(), + onNavigateBackCallback = parameters.getOrNull(), + dispatchersProvider = get(), + buildInfoProvider = get(), + preferenceManager = get(), + getGenerationResultUseCase = get(), + getLastResultFromCacheUseCase = get(), + getGalleryPagedIdsUseCase = get(), + deleteGalleryItemUseCase = get(), + toggleImageVisibilityUseCase = get(), + toggleLikeUseCase = get(), + galleryDetailBitmapExporter = get(), + base64ToBitmapConverter = get(), + schedulersProvider = get(), + generationFormUpdateEvent = get(), + galleryItemStateEvent = get(), + mainRouter = get(), + mediaStoreGateway = get(), + backgroundWorkObserver = get(), + ) + } + + viewModel { parameters -> + ReportViewModel( + itemId = parameters.get(), + sendReportUseCase = get(), + getGenerationResultUseCase = get(), + getLastResultFromCacheUseCase = get(), + base64ToBitmapConverter = get(), + mainRouter = get(), + schedulersProvider = get(), + buildInfoProvider = get(), + ) + } + + viewModel { parameters -> + ImageEditorViewModel( + itemId = parameters.get(), + dispatchersProvider = get(), + getGenerationResultUseCase = get(), + base64ToBitmapConverter = get(), + mediaStoreGateway = get(), + schedulersProvider = get(), + mainRouter = get(), + ) + } + + viewModel { + ImageToImageViewModel( + dispatchersProvider = get(), + generationFormUpdateEvent = get(), + getStableDiffusionSamplersUseCase = get(), + getForgeModulesUseCase = get(), + observeHordeProcessStatusUseCase = get(), + observeLocalDiffusionProcessStatusUseCase = get(), + saveLastResultToCacheUseCase = get(), + saveGenerationResultUseCase = get(), + interruptGenerationUseCase = get(), + drawerRouter = get(), + dimensionValidator = get(), + imageToImageUseCase = get(), + getRandomImageUseCase = get(), + bitmapToBase64Converter = get(), + base64ToBitmapConverter = get(), + preferenceManager = get(), + schedulersProvider = get(), + notificationManager = get(), + wakeLockInterActor = get(), + inPaintStateProducer = get(), + mainRouter = get(), + backgroundTaskManager = get(), + backgroundWorkObserver = get(), + buildInfoProvider = get(), + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/extensions/BooleanExtensions.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/extensions/BooleanExtensions.kt new file mode 100644 index 000000000..359ea94eb --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/extensions/BooleanExtensions.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.presentation.extensions + +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +fun Boolean.mapToUi(): UiText = (if (this) LocalizationR.string.yes else LocalizationR.string.no).asUiText() diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/extensions/NavControllerExtensions.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/extensions/NavControllerExtensions.kt new file mode 100644 index 000000000..859a80494 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/extensions/NavControllerExtensions.kt @@ -0,0 +1,12 @@ +package dev.minios.pdaiv1.presentation.extensions + +import androidx.navigation.NavController +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute + +fun NavController.navigatePopUpToCurrent(navRoute: NavigationRoute) { + navigate(navRoute) { + currentBackStackEntry?.destination?.route?.let { + popUpTo(it) { inclusive = true } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/ModalRenderer.kt similarity index 79% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/ModalRenderer.kt index a25ea9bbd..288215c4c 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/ModalRenderer.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.modal +package dev.minios.pdaiv1.presentation.modal import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ExperimentalMaterial3Api @@ -14,41 +14,42 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext -import com.shifthackz.aisdv1.core.common.extensions.openAppSettings -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.core.ImageToImageIntent -import com.shifthackz.aisdv1.presentation.modal.crop.CropImageModal -import com.shifthackz.aisdv1.presentation.modal.download.DownloadDialog -import com.shifthackz.aisdv1.presentation.modal.embedding.EmbeddingScreen -import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasScreen -import com.shifthackz.aisdv1.presentation.modal.grid.GridBottomSheet -import com.shifthackz.aisdv1.presentation.modal.history.InputHistoryScreen -import com.shifthackz.aisdv1.presentation.modal.language.LanguageBottomSheet -import com.shifthackz.aisdv1.presentation.modal.ldscheduler.LDSchedulerBottomSheet -import com.shifthackz.aisdv1.presentation.modal.tag.EditTagDialog -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuIntent -import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailIntent -import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryIntent -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintIntent -import com.shifthackz.aisdv1.presentation.screen.report.ReportIntent -import com.shifthackz.aisdv1.presentation.screen.settings.SettingsIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.widget.dialog.DecisionInteractiveDialog -import com.shifthackz.aisdv1.presentation.widget.dialog.ErrorDialog -import com.shifthackz.aisdv1.presentation.widget.dialog.GenerationImageBatchResultModal -import com.shifthackz.aisdv1.presentation.widget.dialog.GenerationImageResultDialog -import com.shifthackz.aisdv1.presentation.widget.dialog.InfoDialog -import com.shifthackz.aisdv1.presentation.widget.dialog.ProgressDialog -import com.shifthackz.aisdv1.presentation.widget.dialog.ProgressDialogCancelButton -import com.shifthackz.aisdv1.presentation.widget.input.DropdownTextField +import dev.minios.pdaiv1.core.common.extensions.openAppSettings +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.core.ImageToImageIntent +import dev.minios.pdaiv1.presentation.modal.crop.CropImageModal +import dev.minios.pdaiv1.presentation.modal.download.DownloadDialog +import dev.minios.pdaiv1.presentation.modal.embedding.EmbeddingScreen +import dev.minios.pdaiv1.presentation.modal.extras.ExtrasScreen +import dev.minios.pdaiv1.presentation.modal.grid.GridBottomSheet +import dev.minios.pdaiv1.presentation.modal.history.InputHistoryScreen +import dev.minios.pdaiv1.presentation.modal.language.LanguageBottomSheet +import dev.minios.pdaiv1.presentation.modal.ldscheduler.LDSchedulerBottomSheet +import dev.minios.pdaiv1.presentation.modal.tag.EditTagDialog +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.debug.DebugMenuIntent +import dev.minios.pdaiv1.presentation.screen.falai.FalAiGenerationIntent +import dev.minios.pdaiv1.presentation.screen.gallery.detail.GalleryDetailIntent +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryIntent +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintIntent +import dev.minios.pdaiv1.presentation.screen.report.ReportIntent +import dev.minios.pdaiv1.presentation.screen.settings.SettingsIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.widget.dialog.DecisionInteractiveDialog +import dev.minios.pdaiv1.presentation.widget.dialog.ErrorDialog +import dev.minios.pdaiv1.presentation.widget.dialog.GenerationImageBatchResultModal +import dev.minios.pdaiv1.presentation.widget.dialog.GenerationImageResultDialog +import dev.minios.pdaiv1.presentation.widget.dialog.InfoDialog +import dev.minios.pdaiv1.presentation.widget.dialog.ProgressDialog +import dev.minios.pdaiv1.presentation.widget.dialog.ProgressDialogCancelButton +import dev.minios.pdaiv1.presentation.widget.input.DropdownTextField import com.shifthackz.android.core.mvi.MviIntent -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ModalRenderer( @@ -64,6 +65,7 @@ fun ModalRenderer( processIntent(InPaintIntent.ScreenModal.Dismiss) processIntent(DebugMenuIntent.DismissModal) processIntent(ReportIntent.DismissError) + processIntent(FalAiGenerationIntent.DismissModal) } val context = LocalContext.current when (screenModal) { @@ -237,6 +239,15 @@ fun ModalRenderer( onDismissRequest = dismiss, ) + Modal.DeleteUnlikedConfirm -> DecisionInteractiveDialog( + title = LocalizationR.string.interaction_delete_unliked_title.asUiText(), + text = LocalizationR.string.interaction_delete_unliked_sub_title.asUiText(), + confirmActionResId = LocalizationR.string.yes, + dismissActionResId = LocalizationR.string.no, + onConfirmAction = { processIntent(GalleryIntent.Delete.AllUnliked.Confirm) }, + onDismissRequest = dismiss, + ) + is Modal.ConfirmExport -> DecisionInteractiveDialog( title = LocalizationR.string.interaction_export_title.asUiText(), text = if (screenModal.exportAll) { @@ -256,6 +267,24 @@ fun ModalRenderer( onDismissRequest = dismiss, ) + is Modal.ConfirmSaveToGallery -> DecisionInteractiveDialog( + title = LocalizationR.string.interaction_save_to_gallery_title.asUiText(), + text = if (screenModal.saveAll) { + LocalizationR.string.interaction_save_to_gallery_sub_title.asUiText() + } else { + LocalizationR.string.interaction_save_to_gallery_sub_title_selection.asUiText() + }, + confirmActionResId = LocalizationR.string.action_save, + onConfirmAction = { + if (screenModal.saveAll) { + processIntent(GalleryIntent.SaveToGallery.All.Confirm) + } else { + processIntent(GalleryIntent.SaveToGallery.Selection.Confirm) + } + }, + onDismissRequest = dismiss, + ) + Modal.ExportInProgress -> ProgressDialog( titleResId = LocalizationR.string.exporting_progress_title, subTitleResId = LocalizationR.string.exporting_progress_sub_title, diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/crop/CropImageModal.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/crop/CropImageModal.kt new file mode 100644 index 000000000..0061ca908 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/crop/CropImageModal.kt @@ -0,0 +1,57 @@ +package dev.minios.pdaiv1.presentation.modal.crop + +import android.graphics.Bitmap +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import com.mr0xf00.easycrop.CropError +import com.mr0xf00.easycrop.CropResult +import com.mr0xf00.easycrop.CropperStyle +import com.mr0xf00.easycrop.CropperStyleGuidelines +import com.mr0xf00.easycrop.RectCropShape +import com.mr0xf00.easycrop.crop +import com.mr0xf00.easycrop.rememberImageCropper +import com.mr0xf00.easycrop.ui.ImageCropperDialog +import dev.minios.pdaiv1.core.common.extensions.showToast + +@Composable +fun CropImageModal( + bitmap: Bitmap, + onResult: (Bitmap) -> Unit = {}, + onDismissRequest: () -> Unit = {}, +) { + val imageCropper = rememberImageCropper() + val state = imageCropper.cropState + state?.let { + ImageCropperDialog( + state = it, + style = CropperStyle( + backgroundColor = MaterialTheme.colorScheme.background, + overlay = MaterialTheme.colorScheme.surface, + guidelines = CropperStyleGuidelines(), + shapes = listOf(RectCropShape), + ), + ) + } + val context = LocalContext.current + LaunchedEffect(Unit) { + when (val result = imageCropper.crop(bmp = bitmap.asImageBitmap())) { + is CropResult.Success -> result.bitmap.asAndroidBitmap().let(onResult::invoke) + + CropError.LoadingError -> { + context.showToast("Loading error") + onDismissRequest() + } + + CropError.SavingError -> { + context.showToast("Saving error") + onDismissRequest() + } + + CropResult.Cancelled -> onDismissRequest() + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialog.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialog.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialog.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialog.kt index 31477a1fd..71ec73f5b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialog.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialog.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.download +package dev.minios.pdaiv1.presentation.modal.download import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -42,13 +42,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import com.shifthackz.aisdv1.presentation.R +import dev.minios.pdaiv1.presentation.R import com.shifthackz.android.core.mvi.MviComponent import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR private const val GITHUB_WEB_RESOURCE = "github.com" -private const val SDAI_WEB_RESOURCE = "share.moroz.cc" +private const val PDAI_WEB_RESOURCE = "share.minios.dev" @Composable fun DownloadDialog( @@ -188,9 +188,9 @@ private fun ScreenContent( contentDescription = null, ) - SDAI_WEB_RESOURCE -> Image( + PDAI_WEB_RESOURCE -> Image( modifier = iconModifier, - painter = painterResource(R.drawable.ic_sdai_logo), + painter = painterResource(R.drawable.ic_pdai_logo), contentDescription = null, ) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogEffect.kt similarity index 79% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogEffect.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogEffect.kt index d672f7832..5d87d0893 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogEffect.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogEffect.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.download +package dev.minios.pdaiv1.presentation.modal.download import com.shifthackz.android.core.mvi.MviEffect diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogIntent.kt similarity index 85% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogIntent.kt index 6940e12b5..0817a0bd6 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogIntent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.download +package dev.minios.pdaiv1.presentation.modal.download import com.shifthackz.android.core.mvi.MviIntent diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogState.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogState.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogState.kt index 70d3788ad..8a56fdbbd 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogState.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogState.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.download +package dev.minios.pdaiv1.presentation.modal.download import androidx.compose.runtime.Immutable import com.shifthackz.android.core.mvi.MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogViewModel.kt new file mode 100644 index 000000000..42b252eab --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/download/DownloadDialogViewModel.kt @@ -0,0 +1,48 @@ +package dev.minios.pdaiv1.presentation.modal.download + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalModelUseCase +import io.reactivex.rxjava3.kotlin.subscribeBy + +class DownloadDialogViewModel( + private val getLocalModelUseCase: GetLocalModelUseCase, + private val schedulersProvider: SchedulersProvider, + dispatchersProvider: DispatchersProvider, +) : MviRxViewModel() { + + override val initialState = DownloadDialogState() + + override val effectDispatcher = dispatchersProvider.immediate + + override fun processIntent(intent: DownloadDialogIntent) { + when (intent) { + is DownloadDialogIntent.LoadModelData -> !getLocalModelUseCase(intent.id) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { model -> + // For QNN models with single source, auto-start download + if (model.sources.size == 1) { + emitEffect(DownloadDialogEffect.StartDownload(model.sources.first())) + emitEffect(DownloadDialogEffect.Close) + } else { + updateState { + it.copy(sources = model.sources.mapIndexed { i, url -> url to (i == 0) }) + } + } + } + + is DownloadDialogIntent.SelectSource -> updateState { + it.copy(sources = it.sources.map { (url, _) -> url to (url == intent.url) }) + } + + DownloadDialogIntent.StartDownload -> emitEffect( + DownloadDialogEffect.StartDownload(currentState.selectedUrl) + ) + + DownloadDialogIntent.Close -> emitEffect(DownloadDialogEffect.Close) + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingIntent.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingIntent.kt index be0e9a14d..d70156e48 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingIntent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.embedding +package dev.minios.pdaiv1.presentation.modal.embedding import com.shifthackz.android.core.mvi.MviIntent diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingScreen.kt similarity index 95% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingScreen.kt index 04d691026..5c229be68 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingScreen.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.embedding +package dev.minios.pdaiv1.presentation.modal.embedding import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -45,17 +45,17 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import com.shifthackz.aisdv1.core.extensions.shimmer -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasEffect -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.presentation.widget.error.ErrorComposable -import com.shifthackz.aisdv1.presentation.widget.source.getName -import com.shifthackz.aisdv1.presentation.widget.toolbar.ModalDialogToolbar +import dev.minios.pdaiv1.core.extensions.shimmer +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.modal.extras.ExtrasEffect +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.presentation.widget.error.ErrorComposable +import dev.minios.pdaiv1.presentation.widget.source.getName +import dev.minios.pdaiv1.presentation.widget.toolbar.ModalDialogToolbar import com.shifthackz.android.core.mvi.MviComponent import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR -import com.shifthackz.aisdv1.presentation.R as PresentationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.R as PresentationR @Composable fun EmbeddingScreen( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingState.kt similarity index 76% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingState.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingState.kt index 9898c607a..bc8e1b689 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingState.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingState.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.modal.embedding +package dev.minios.pdaiv1.presentation.modal.embedding import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.model.ErrorState +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.model.ErrorState import com.shifthackz.android.core.mvi.MviState @Immutable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingViewModel.kt similarity index 83% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingViewModel.kt index c01ebc72e..0e78c4db9 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingViewModel.kt @@ -1,15 +1,15 @@ -package com.shifthackz.aisdv1.presentation.modal.embedding +package dev.minios.pdaiv1.presentation.modal.embedding -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCase -import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasEffect -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.presentation.utils.ExtrasFormatter +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCase +import dev.minios.pdaiv1.presentation.modal.extras.ExtrasEffect +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.presentation.utils.ExtrasFormatter import io.reactivex.rxjava3.kotlin.subscribeBy class EmbeddingViewModel( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasEffect.kt similarity index 81% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasEffect.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasEffect.kt index 5a8a2812b..be0b7affa 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasEffect.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasEffect.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.extras +package dev.minios.pdaiv1.presentation.modal.extras import com.shifthackz.android.core.mvi.MviEffect diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasIntent.kt similarity index 81% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasIntent.kt index 9d4463b5b..c788e5615 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasIntent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.extras +package dev.minios.pdaiv1.presentation.modal.extras import com.shifthackz.android.core.mvi.MviIntent diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasScreen.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasScreen.kt index ffa0c4db7..84ad680a8 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasScreen.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.extras +package dev.minios.pdaiv1.presentation.modal.extras import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -41,16 +41,16 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import com.shifthackz.aisdv1.core.extensions.shimmer -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.aisdv1.presentation.widget.error.ErrorComposable -import com.shifthackz.aisdv1.presentation.widget.source.getName -import com.shifthackz.aisdv1.presentation.widget.toolbar.ModalDialogToolbar +import dev.minios.pdaiv1.core.extensions.shimmer +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.widget.error.ErrorComposable +import dev.minios.pdaiv1.presentation.widget.source.getName +import dev.minios.pdaiv1.presentation.widget.toolbar.ModalDialogToolbar import com.shifthackz.android.core.mvi.MviComponent import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ExtrasScreen( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasState.kt new file mode 100644 index 000000000..e510fab4d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasState.kt @@ -0,0 +1,28 @@ +package dev.minios.pdaiv1.presentation.modal.extras + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.presentation.model.ExtraType +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class ExtrasState( + val loading: Boolean = true, + val source: ServerSource = ServerSource.AUTOMATIC1111, + val error: ErrorState = ErrorState.None, + val prompt: String = "", + val negativePrompt: String = "", + val type: ExtraType = ExtraType.Lora, + val loras: List = emptyList(), +) : MviState + +@Immutable +data class ExtraItemUi( + val type: ExtraType, + val key: String, + val name: String, + val alias: String?, + val isApplied: Boolean, + val value: String? = null, +) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasViewModel.kt similarity index 81% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasViewModel.kt index ef76ad356..b09d57cd8 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasViewModel.kt @@ -1,19 +1,19 @@ -package com.shifthackz.aisdv1.presentation.modal.extras +package dev.minios.pdaiv1.presentation.modal.extras -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.LoRA -import com.shifthackz.aisdv1.domain.entity.StableDiffusionHyperNetwork -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCase -import com.shifthackz.aisdv1.domain.usecase.sdlora.FetchAndGetLorasUseCase -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.aisdv1.presentation.utils.ExtrasFormatter +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.LoRA +import dev.minios.pdaiv1.domain.entity.StableDiffusionHyperNetwork +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCase +import dev.minios.pdaiv1.domain.usecase.sdlora.FetchAndGetLorasUseCase +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.utils.ExtrasFormatter import io.reactivex.rxjava3.kotlin.subscribeBy class ExtrasViewModel( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/grid/GridBottomSheet.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/grid/GridBottomSheet.kt similarity index 80% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/grid/GridBottomSheet.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/grid/GridBottomSheet.kt index 19682cf97..68a79e6c0 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/grid/GridBottomSheet.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/grid/GridBottomSheet.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.grid +package dev.minios.pdaiv1.presentation.modal.grid import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -9,11 +9,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.presentation.widget.item.GridIcon -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.presentation.widget.item.GridIcon +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun GridBottomSheet( @@ -31,10 +31,12 @@ fun GridBottomSheet( Grid.entries.forEach { grid -> val textCount = stringResource( id = when (grid) { + Grid.Fixed1 -> LocalizationR.string.one Grid.Fixed2 -> LocalizationR.string.two Grid.Fixed3 -> LocalizationR.string.three Grid.Fixed4 -> LocalizationR.string.four Grid.Fixed5 -> LocalizationR.string.five + Grid.Fixed6 -> LocalizationR.string.six }, ) SettingsItem( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryItemUi.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryItemUi.kt new file mode 100644 index 000000000..21ba8ac6e --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryItemUi.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.presentation.modal.history + +import android.graphics.Bitmap +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.domain.entity.AiGenerationResult + +@Immutable +data class InputHistoryItemUi( + val generationResult: AiGenerationResult, + val bitmap: Bitmap, +) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryPagingSource.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryPagingSource.kt similarity index 77% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryPagingSource.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryPagingSource.kt index c34118610..1a5c8eea1 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryPagingSource.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryPagingSource.kt @@ -1,16 +1,16 @@ -package com.shifthackz.aisdv1.presentation.modal.history +package dev.minios.pdaiv1.presentation.modal.history import androidx.paging.PagingSource import androidx.paging.PagingState import androidx.paging.rxjava3.RxPagingSource -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Input -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Output -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase -import com.shifthackz.aisdv1.presentation.utils.Constants +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter.Input +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter.Output +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCase +import dev.minios.pdaiv1.presentation.utils.Constants import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryScreen.kt similarity index 97% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryScreen.kt index b1935ec7d..5117d60fc 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/history/InputHistoryScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalLayoutApi::class) -package com.shifthackz.aisdv1.presentation.modal.history +package dev.minios.pdaiv1.presentation.modal.history import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -40,10 +40,10 @@ import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.items import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.AiGenerationResult import kotlinx.coroutines.flow.Flow import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun InputHistoryScreen( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryViewModel.kt new file mode 100644 index 000000000..e709301ae --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryViewModel.kt @@ -0,0 +1,47 @@ +package dev.minios.pdaiv1.presentation.modal.history + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCase +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryPagingSource +import dev.minios.pdaiv1.presentation.utils.Constants +import com.shifthackz.android.core.mvi.EmptyEffect +import com.shifthackz.android.core.mvi.EmptyIntent +import com.shifthackz.android.core.mvi.EmptyState +import kotlinx.coroutines.flow.Flow + +class InputHistoryViewModel( + dispatchersProvider: DispatchersProvider, + private val getGenerationResultPagedUseCase: GetGenerationResultPagedUseCase, + private val base64ToBitmapConverter: Base64ToBitmapConverter, + private val schedulersProvider: SchedulersProvider, +) : MviRxViewModel() { + + override val initialState = EmptyState + + override val effectDispatcher = dispatchersProvider.immediate + + private val config = PagingConfig( + pageSize = Constants.PAGINATION_PAYLOAD_SIZE, + initialLoadSize = Constants.PAGINATION_PAYLOAD_SIZE + ) + + private val pager: Pager = Pager( + config = config, + initialKey = GalleryPagingSource.FIRST_KEY, + pagingSourceFactory = { + InputHistoryPagingSource( + getGenerationResultPagedUseCase, + base64ToBitmapConverter, + schedulersProvider, + ) + } + ) + + val pagingFlow: Flow> = pager.flow +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/language/LanguageBottomSheet.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/language/LanguageBottomSheet.kt similarity index 87% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/language/LanguageBottomSheet.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/language/LanguageBottomSheet.kt index b18150d7d..778a36666 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/language/LanguageBottomSheet.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/language/LanguageBottomSheet.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.language +package dev.minios.pdaiv1.presentation.modal.language import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.Image @@ -13,10 +13,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.os.LocaleListCompat -import com.shifthackz.aisdv1.core.localization.Localization -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.Localization +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable @Preview diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt index 8d5b49eb3..c940671cd 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.ldscheduler +package dev.minios.pdaiv1.presentation.modal.ldscheduler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -10,9 +10,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken -import com.shifthackz.aisdv1.presentation.screen.debug.mapToUi -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.presentation.screen.debug.mapToUi +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem @Composable @Preview diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagDialog.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagDialog.kt similarity index 90% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagDialog.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagDialog.kt index 92740dccf..500dff8cb 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagDialog.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagDialog.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.tag +package dev.minios.pdaiv1.presentation.modal.tag import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -27,18 +27,18 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.aisdv1.presentation.theme.sliderColors -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.utils.Constants.EXTRA_MAXIMUM -import com.shifthackz.aisdv1.presentation.utils.Constants.EXTRA_MINIMUM -import com.shifthackz.aisdv1.presentation.utils.Constants.EXTRA_STEP -import com.shifthackz.aisdv1.presentation.widget.input.SliderTextInputField -import com.shifthackz.aisdv1.presentation.widget.input.chip.ChipTextFieldItem -import com.shifthackz.aisdv1.presentation.widget.toolbar.ModalDialogToolbar +import dev.minios.pdaiv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.utils.Constants.EXTRA_MAXIMUM +import dev.minios.pdaiv1.presentation.utils.Constants.EXTRA_MINIMUM +import dev.minios.pdaiv1.presentation.utils.Constants.EXTRA_STEP +import dev.minios.pdaiv1.presentation.widget.input.SliderTextInputField +import dev.minios.pdaiv1.presentation.widget.input.chip.ChipTextFieldItem +import dev.minios.pdaiv1.presentation.widget.toolbar.ModalDialogToolbar import org.koin.androidx.compose.koinViewModel import kotlin.math.abs -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun EditTagDialog( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagEffect.kt similarity index 82% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagEffect.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagEffect.kt index 04bb9a388..c32d1b461 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagEffect.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagEffect.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.tag +package dev.minios.pdaiv1.presentation.modal.tag import com.shifthackz.android.core.mvi.MviEffect diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagIntent.kt similarity index 91% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagIntent.kt index 708199c2a..0bcd2a721 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagIntent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.modal.tag +package dev.minios.pdaiv1.presentation.modal.tag import com.shifthackz.android.core.mvi.MviIntent diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagState.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagState.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagState.kt index 24f0f30b3..3b9e5ea5b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagState.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagState.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.modal.tag +package dev.minios.pdaiv1.presentation.modal.tag import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.common.math.roundTo -import com.shifthackz.aisdv1.presentation.model.ExtraType +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.presentation.model.ExtraType import com.shifthackz.android.core.mvi.MviState @Immutable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagViewModel.kt similarity index 90% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagViewModel.kt index 7f4e98c3a..6657e2adb 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagViewModel.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.presentation.modal.tag +package dev.minios.pdaiv1.presentation.modal.tag -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.aisdv1.presentation.utils.ExtrasFormatter +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.utils.ExtrasFormatter class EditTagViewModel( dispatchersProvider: DispatchersProvider, diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/AspectRatio.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/AspectRatio.kt new file mode 100644 index 000000000..6bfdcb817 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/AspectRatio.kt @@ -0,0 +1,35 @@ +package dev.minios.pdaiv1.presentation.model + +enum class AspectRatio( + val widthRatio: Int, + val heightRatio: Int, + val label: String, +) { + RATIO_1_1(1, 1, "1:1"), + RATIO_4_3(4, 3, "4:3"), + RATIO_3_4(3, 4, "3:4"), + RATIO_16_9(16, 9, "16:9"), + RATIO_9_16(9, 16, "9:16"), + RATIO_3_2(3, 2, "3:2"), + RATIO_2_3(2, 3, "2:3"), + RATIO_21_9(21, 9, "21:9"), + RATIO_9_21(9, 21, "9:21"); + + fun calculateDimensions(baseSize: Int): Pair { + val gcd = gcd(widthRatio, heightRatio) + val normalizedWidth = widthRatio / gcd + val normalizedHeight = heightRatio / gcd + + // Always use baseSize as width + val width = baseSize + val height = (baseSize * normalizedHeight) / normalizedWidth + // Round to nearest 8 for better compatibility + return Pair(roundTo8(width), roundTo8(height)) + } + + private fun gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) + + private fun roundTo8(value: Int): Int = ((value + 4) / 8) * 8 + + override fun toString(): String = label +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/ErrorState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/ErrorState.kt new file mode 100644 index 000000000..5c2a97f6c --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/ErrorState.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.presentation.model + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.core.model.UiText +import com.shifthackz.android.core.mvi.MviState + +sealed interface ErrorState : MviState { + + data object None : ErrorState + + data object Generic : ErrorState + + @Immutable + data class WithMessage(val message: UiText) : ErrorState +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/ExtraType.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/ExtraType.kt new file mode 100644 index 000000000..d9515a053 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/ExtraType.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.presentation.model + +enum class ExtraType(val raw: String) { + Lora("lora"), + HyperNet("hyper_net"); +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/FalAiState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/FalAiState.kt new file mode 100644 index 000000000..61e09be9a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/FalAiState.kt @@ -0,0 +1,92 @@ +package dev.minios.pdaiv1.presentation.model + +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiInputProperty +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType + +/** + * UI representation of a FalAi endpoint for display. + */ +data class FalAiEndpointUi( + val id: String, + val endpointId: String, + val title: String, + val description: String, + val category: String, + val group: String, // For hierarchical display + val thumbnailUrl: String, + val isCustom: Boolean, + val properties: List, +) + +/** + * UI representation of an input property with current value. + */ +data class FalAiPropertyUi( + val name: String, + val title: String, + val description: String, + val type: FalAiPropertyType, + val required: Boolean, + val defaultValue: Any?, + val currentValue: Any?, + val minimum: Double?, + val maximum: Double?, + val enumValues: List, + val isAdvanced: Boolean, + val arrayItemProperties: List?, + val linkedMaskProperty: String? = null, // For inpainting: links image_url to its mask_url +) { + companion object { + fun fromDomain(property: FalAiInputProperty, isRequired: Boolean): FalAiPropertyUi { + // For INPAINT type, it's a main property (not advanced) + val isAdvanced = property.type != FalAiPropertyType.INPAINT + && property.name !in listOf("prompt", "image_url", "mask_url") + && !property.name.contains("image") + && !property.name.contains("mask") + + return FalAiPropertyUi( + name = property.name, + title = property.title.ifBlank { property.name.replace("_", " ").replaceFirstChar { it.uppercase() } }, + description = property.description, + type = property.type, + required = isRequired, + defaultValue = property.default, + currentValue = property.default, + minimum = property.minimum?.toDouble(), + maximum = property.maximum?.toDouble(), + enumValues = property.enumValues ?: emptyList(), + isAdvanced = isAdvanced, + arrayItemProperties = property.arrayItemProperties?.map { itemProp -> + fromDomain(itemProp, itemProp.isRequired) + }, + linkedMaskProperty = property.linkedMaskProperty, + ) + } + } +} + +/** + * State for FalAi generation form. + */ +data class FalAiFormState( + val endpoints: List = emptyList(), + val selectedEndpoint: FalAiEndpointUi? = null, + val propertyValues: Map = emptyMap(), + val advancedOptionsVisible: Boolean = false, + val isLoading: Boolean = false, +) + +fun FalAiEndpoint.toUi(): FalAiEndpointUi = FalAiEndpointUi( + id = id, + endpointId = endpointId, + title = title, + description = description, + category = category.displayName, + group = group, + thumbnailUrl = thumbnailUrl, + isCustom = isCustom, + properties = schema.inputProperties.map { prop -> + FalAiPropertyUi.fromDomain(prop, schema.requiredProperties.contains(prop.name)) + }, +) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/InPaintModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/InPaintModel.kt similarity index 89% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/InPaintModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/model/InPaintModel.kt index f6bd2ab5a..ff06ca386 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/InPaintModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/InPaintModel.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.model +package dev.minios.pdaiv1.presentation.model import android.graphics.Bitmap import androidx.compose.runtime.Immutable @@ -29,8 +29,8 @@ data class InPaintModel( } enum class Area(val fullRes: Boolean) { - WholePicture(true), - OnlyMasked(false); + WholePicture(false), + OnlyMasked(true); } fun clear(): InPaintModel = copy( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/LaunchSource.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/LaunchSource.kt similarity index 77% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/LaunchSource.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/model/LaunchSource.kt index 14fb15459..1a1c6a324 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/LaunchSource.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/LaunchSource.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.model +package dev.minios.pdaiv1.presentation.model enum class LaunchSource { SPLASH, diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/Modal.kt similarity index 83% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/model/Modal.kt index 98983733b..0e8ac8640 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/Modal.kt @@ -1,15 +1,15 @@ -package com.shifthackz.aisdv1.presentation.model +package dev.minios.pdaiv1.presentation.model import android.graphics.Bitmap import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.feature.diffusion.LocalDiffusion +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState sealed interface Modal { @@ -24,8 +24,12 @@ sealed interface Modal { val isMultiple: Boolean, ) : Modal + data object DeleteUnlikedConfirm : Modal + data class ConfirmExport(val exportAll: Boolean) : Modal + data class ConfirmSaveToGallery(val saveAll: Boolean) : Modal + data object ExportInProgress : Modal data object ConnectLocalHost : Modal diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/MotionEvent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/MotionEvent.kt new file mode 100644 index 000000000..e09ec9be5 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/MotionEvent.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.presentation.model + +enum class MotionEvent { + Idle, Down, Move, Up +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/NavItem.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/NavItem.kt similarity index 82% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/NavItem.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/model/NavItem.kt index f875f6c6c..7fae6a023 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/NavItem.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/NavItem.kt @@ -1,12 +1,12 @@ -package com.shifthackz.aisdv1.presentation.model +package dev.minios.pdaiv1.presentation.model import androidx.annotation.DrawableRes import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute @Immutable data class NavItem( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/QnnResolution.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/QnnResolution.kt new file mode 100644 index 000000000..2abe818eb --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/model/QnnResolution.kt @@ -0,0 +1,150 @@ +package dev.minios.pdaiv1.presentation.model + +/** + * Represents available resolutions for QNN models. + * These are based on compiled models and patch files. + * Base model is 512x512, other resolutions require .patch files. + */ +enum class QnnResolution( + val width: Int, + val height: Int, + val patchFileName: String? = null, + val cpuOnly: Boolean = false, +) { + // CPU-only resolutions (for MNN models) + RES_256x256(256, 256, null, cpuOnly = true), + RES_256x384(256, 384, null, cpuOnly = true), + RES_384x256(384, 256, null, cpuOnly = true), + RES_384x384(384, 384, null, cpuOnly = true), + + // NPU resolutions (for QNN models, also available for CPU) + RES_512x512(512, 512, null), + RES_512x768(512, 768, "512x768.patch"), + RES_768x512(768, 512, "768x512.patch"), + RES_768x768(768, 768, "768.patch"), + RES_768x1024(768, 1024, "768x1024.patch"), + RES_1024x768(1024, 768, "1024x768.patch"), + RES_1024x1024(1024, 1024, "1024.patch"); + + val displayName: String + get() = "${width}×${height}" + + companion object { + /** + * Default resolution for QNN NPU models. + */ + val DEFAULT = RES_512x512 + + /** + * Default resolution for CPU/MNN models. + */ + val DEFAULT_CPU = RES_256x256 + + /** + * Find QnnResolution by width and height. + * Returns null if resolution is not supported. + */ + fun fromDimensions(width: Int, height: Int): QnnResolution? { + return entries.find { it.width == width && it.height == height } + } + + /** + * Check if given dimensions are valid for QNN. + */ + fun isValidResolution(width: Int, height: Int): Boolean { + return fromDimensions(width, height) != null + } + + /** + * Get resolutions available for NPU models (excludes CPU-only resolutions). + */ + fun npuResolutions(): List = entries.filter { !it.cpuOnly } + + /** + * Get resolutions available for CPU/MNN models (all resolutions are available). + */ + fun cpuResolutions(): List = entries.toList() + + /** + * Get resolutions for specific model type. + * @param runOnCpu If true, returns CPU resolutions; otherwise returns NPU resolutions. + */ + fun forModelType(runOnCpu: Boolean): List { + return if (runOnCpu) cpuResolutions() else npuResolutions() + } + + /** + * Get default resolution for specific model type. + */ + fun defaultForModelType(runOnCpu: Boolean): QnnResolution { + return if (runOnCpu) DEFAULT_CPU else DEFAULT + } + + /** + * Get resolutions for img2img mode. + * CPU/GPU mode is limited to max 512x512 for stability. + * NPU mode supports all NPU resolutions. + * @param runOnCpu If true, returns CPU resolutions up to 512x512; otherwise returns NPU resolutions. + */ + fun forImg2Img(runOnCpu: Boolean): List { + return if (runOnCpu) { + // CPU/GPU img2img is limited to max 512x512 for memory stability + cpuResolutions().filter { it.width <= 512 && it.height <= 512 } + } else { + npuResolutions() + } + } + + /** + * Get all available resolutions as dimension strings. + */ + val availableDimensionStrings: List + get() = entries.map { "${it.width}x${it.height}" } + + /** + * Check if Hires.Fix is available for a given base resolution. + * Hires is available only for NPU and only for square resolutions. + */ + fun isHiresAvailable(baseResolution: QnnResolution, runOnCpu: Boolean): Boolean { + if (runOnCpu) return false + return hiresTargetResolutions(baseResolution).isNotEmpty() + } + + /** + * Get base resolutions that support Hires.Fix (square resolutions with larger targets). + * Only for NPU. + */ + fun hiresBaseResolutions(): List { + return npuResolutions().filter { base -> + hiresTargetResolutions(base).isNotEmpty() + } + } + + /** + * Get valid target resolutions for Hires.Fix given a base resolution. + * Only available for square resolutions (1:1 aspect ratio). + * + * Supported upscale paths: + * - 512x512 → 768x768, 1024x1024 + * - 768x768 → 1024x1024 + */ + fun hiresTargetResolutions(baseResolution: QnnResolution): List { + // Only square resolutions support Hires.Fix + if (baseResolution.width != baseResolution.height) return emptyList() + + return npuResolutions().filter { target -> + // Target must be square AND strictly larger + target.width == target.height && + target.width > baseResolution.width + } + } + + /** + * Get default Hires target resolution for a given base. + * Returns the largest available target with same aspect ratio, or null if none. + */ + fun defaultHiresTarget(baseResolution: QnnResolution): QnnResolution? { + return hiresTargetResolutions(baseResolution).maxByOrNull { it.width * it.height } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/NavigationEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/NavigationEffect.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/NavigationEffect.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/NavigationEffect.kt index f66ec403b..f1504a06a 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/NavigationEffect.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/NavigationEffect.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.navigation +package dev.minios.pdaiv1.presentation.navigation import androidx.navigation.NavOptionsBuilder import com.shifthackz.android.core.mvi.MviEffect diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/NavigationRoute.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/NavigationRoute.kt similarity index 79% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/NavigationRoute.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/NavigationRoute.kt index d898633bf..2eefdb988 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/NavigationRoute.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/NavigationRoute.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.presentation.navigation +package dev.minios.pdaiv1.presentation.navigation -import com.shifthackz.aisdv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.LaunchSource import kotlinx.serialization.Serializable sealed interface NavigationRoute { @@ -34,6 +34,12 @@ sealed interface NavigationRoute { @Serializable data class GalleryDetail(val itemId: Long) : NavigationRoute + @Serializable + data object GalleryFull : NavigationRoute + + @Serializable + data class ImageEditor(val itemId: Long) : NavigationRoute + @Serializable data class ReportImage(val itemId: Long) : NavigationRoute @@ -47,6 +53,9 @@ sealed interface NavigationRoute { @Serializable data object ImgToImg : HomeNavigation + @Serializable + data object FalAi : HomeNavigation + @Serializable data object Gallery : HomeNavigation diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/SharedTransitionProvider.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/SharedTransitionProvider.kt new file mode 100644 index 000000000..2f7928c52 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/SharedTransitionProvider.kt @@ -0,0 +1,62 @@ +@file:OptIn(ExperimentalSharedTransitionApi::class) + +package dev.minios.pdaiv1.presentation.navigation + +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf + +/** + * CompositionLocal for SharedTransitionScope - used for Immich-style hero animations + * between gallery grid and detail view. + */ +val LocalSharedTransitionScope = compositionLocalOf { null } + +/** + * CompositionLocal for AnimatedVisibilityScope - needed for sharedElement modifier + */ +val LocalAnimatedVisibilityScope = compositionLocalOf { null } + +/** + * CompositionLocal for controlling bottom navigation bar visibility. + * When true, the bottom navigation bar in HomeNavigationScreen should be hidden. + * This is used during full-screen image viewing in gallery detail. + */ +val LocalHideBottomNavigation = compositionLocalOf { false } + +/** + * Callback to update the bottom navigation visibility state. + * Call with true to hide, false to show. + */ +val LocalSetHideBottomNavigation = compositionLocalOf<((Boolean) -> Unit)?> { null } + +/** + * Wrapper that provides SharedTransitionLayout scope to the entire navigation tree. + * This enables shared element transitions between screens. + */ +@Composable +fun SharedTransitionProvider( + content: @Composable SharedTransitionScope.() -> Unit +) { + SharedTransitionLayout { + CompositionLocalProvider( + LocalSharedTransitionScope provides this + ) { + content() + } + } +} + +/** + * Helper to create shared element key for gallery items + */ +fun galleryItemSharedKey(itemId: Long) = "gallery_item_$itemId" + +/** + * Helper to create shared element key for gallery item image + */ +fun galleryImageSharedKey(itemId: Long) = "gallery_image_$itemId" diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/DrawerNavGraph.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/DrawerNavGraph.kt new file mode 100644 index 000000000..bdfdb0ffa --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/DrawerNavGraph.kt @@ -0,0 +1,63 @@ +package dev.minios.pdaiv1.presentation.navigation.graph + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DeveloperMode +import androidx.compose.material.icons.filled.SettingsEthernet +import androidx.compose.material.icons.filled.Web +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.FeatureTag +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.NavItem +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.widget.source.getNameUiText +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +fun mainDrawerNavItems(settings: Settings? = null): List = buildList { + if (settings?.source == ServerSource.FAL_AI) { + add(falAiTab()) + } else { + add(txt2ImgTab().copy(name = LocalizationR.string.title_text_to_image.asUiText())) + add(img2imgTab().copy(name = LocalizationR.string.title_image_to_image.asUiText())) + } + add(galleryTab()) + settings?.source?.takeIf { it.featureTags.contains(FeatureTag.OwnServer) }?.let { + add(webUi(it)) + } + add(settingsTab()) + add(configuration()) + settings?.developerMode?.takeIf { it }?.let { + add(developerMode()) + } +} + +private fun webUi(source: ServerSource) = NavItem( + name = UiText.Concat( + LocalizationR.string.drawer_web_ui.asUiText(), + " (".asUiText(), + source.getNameUiText(), + ")".asUiText(), + ), + navRoute = NavigationRoute.WebUi, + icon = NavItem.Icon.Vector( + vector = Icons.Default.Web, + ), +) + +private fun configuration() = NavItem( + name = LocalizationR.string.settings_item_config.asUiText(), + navRoute = NavigationRoute.ServerSetup(source = LaunchSource.SETTINGS), + icon = NavItem.Icon.Vector( + vector = Icons.Default.SettingsEthernet, + ), +) + +private fun developerMode() = NavItem( + name = LocalizationR.string.title_debug_menu.asUiText(), + navRoute = NavigationRoute.Debug, + icon = NavItem.Icon.Vector( + vector = Icons.Default.DeveloperMode, + ) +) diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/HomeNavGraph.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/HomeNavGraph.kt new file mode 100644 index 000000000..7722b6866 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/HomeNavGraph.kt @@ -0,0 +1,151 @@ +package dev.minios.pdaiv1.presentation.navigation.graph + +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.presentation.model.NavItem +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.navigation.router.home.HomeRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.screen.falai.FalAiGenerationScreen +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryScreen +import dev.minios.pdaiv1.presentation.screen.home.HomeNavigationScreen +import dev.minios.pdaiv1.presentation.screen.img2img.ImageToImageScreen +import dev.minios.pdaiv1.presentation.screen.settings.SettingsScreen +import dev.minios.pdaiv1.presentation.screen.txt2img.TextToImageScreen +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.Disposable +import org.koin.compose.koinInject +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.R as PresentationR + +fun NavGraphBuilder.homeScreenNavGraph() { + composable { + val preferenceManager: PreferenceManager = koinInject() + val mainRouter: MainRouter = koinInject() + var serverSource by remember { mutableStateOf(preferenceManager.source) } + + DisposableEffect(preferenceManager) { + val disposable: Disposable = preferenceManager + .observe() + .map { it.source } + .distinctUntilChanged() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { source -> serverSource = source } + + onDispose { disposable.dispose() } + } + + val navItems = when (serverSource) { + ServerSource.FAL_AI -> listOf( + falAiTab(), + galleryTab(), + settingsTab(), + ) + else -> listOf( + txt2ImgTab(), + img2imgTab(), + galleryTab(), + settingsTab(), + ) + } + + HomeNavigationScreen(navItems = navItems) + } +} + +fun txt2ImgTab() = NavItem( + name = LocalizationR.string.home_tab_txt_to_img.asUiText(), + navRoute = NavigationRoute.HomeNavigation.TxtToImg, + icon = NavItem.Icon.Resource( + resId = PresentationR.drawable.ic_text, + modifier = Modifier.size(24.dp), + ), + content = { + HomeTabBase(NavigationRoute.HomeNavigation.TxtToImg) { + TextToImageScreen() + } + }, +) + +fun img2imgTab() = NavItem( + name = LocalizationR.string.home_tab_img_to_img.asUiText(), + navRoute = NavigationRoute.HomeNavigation.ImgToImg, + icon = NavItem.Icon.Resource( + resId = PresentationR.drawable.ic_image, + modifier = Modifier.size(24.dp), + ), + content = { + HomeTabBase(NavigationRoute.HomeNavigation.ImgToImg) { + ImageToImageScreen() + } + }, +) + +fun galleryTab() = NavItem( + name = LocalizationR.string.home_tab_gallery.asUiText(), + navRoute = NavigationRoute.HomeNavigation.Gallery, + icon = NavItem.Icon.Resource( + resId = PresentationR.drawable.ic_gallery, + modifier = Modifier.size(24.dp), + ), + content = { + // Gallery as a regular tab within Home + HomeTabBase(NavigationRoute.HomeNavigation.Gallery) { + GalleryScreen() + } + }, +) + +fun falAiTab() = NavItem( + name = "Fal AI".asUiText(), + navRoute = NavigationRoute.HomeNavigation.FalAi, + icon = NavItem.Icon.Resource( + resId = PresentationR.drawable.ic_text, + modifier = Modifier.size(24.dp), + ), + content = { + HomeTabBase(NavigationRoute.HomeNavigation.FalAi) { + FalAiGenerationScreen() + } + }, +) + +fun settingsTab() = NavItem( + name = LocalizationR.string.home_tab_settings.asUiText(), + navRoute = NavigationRoute.HomeNavigation.Settings, + icon = NavItem.Icon.Vector( + vector = Icons.Default.Settings, + ), + content = { + HomeTabBase(NavigationRoute.HomeNavigation.Settings) { + SettingsScreen() + } + } +) + +@Composable +private fun HomeTabBase( + navRoute: NavigationRoute, + content: @Composable () -> Unit, +) { + val homeRouter: HomeRouter = koinInject() + LaunchedEffect(Unit) { + homeRouter.updateExternallyWithoutNavigation(navRoute = navRoute) + } + content() +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/MainNavGraph.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/MainNavGraph.kt new file mode 100644 index 000000000..8a8b70149 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/graph/MainNavGraph.kt @@ -0,0 +1,140 @@ +package dev.minios.pdaiv1.presentation.navigation.graph + +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.runtime.CompositionLocalProvider +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.navigation.LocalAnimatedVisibilityScope +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.screen.debug.DebugMenuScreen +import dev.minios.pdaiv1.presentation.screen.donate.DonateScreen +import dev.minios.pdaiv1.presentation.screen.gallery.detail.GalleryDetailScreen +import dev.minios.pdaiv1.presentation.screen.gallery.editor.ImageEditorScreen +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryScreen +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintScreen +import dev.minios.pdaiv1.presentation.screen.loader.ConfigurationLoaderScreen +import dev.minios.pdaiv1.presentation.screen.logger.LoggerScreen +import dev.minios.pdaiv1.presentation.screen.onboarding.OnBoardingScreen +import dev.minios.pdaiv1.presentation.screen.onboarding.OnBoardingViewModel +import dev.minios.pdaiv1.presentation.screen.report.ReportScreen +import dev.minios.pdaiv1.presentation.screen.report.ReportViewModel +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupScreen +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupViewModel +import dev.minios.pdaiv1.presentation.screen.splash.SplashScreen +import dev.minios.pdaiv1.presentation.screen.web.webui.WebUiScreen +import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject +import org.koin.core.parameter.parametersOf +import kotlin.reflect.typeOf + +@OptIn(ExperimentalSharedTransitionApi::class) +fun NavGraphBuilder.mainNavGraph() { + + composable { + SplashScreen() + } + + composable( + typeMap = mapOf( + typeOf() to NavType.EnumType(LaunchSource::class.java) + ) + ) { entry -> + val sourceKey = entry.toRoute().source.ordinal + ServerSetupScreen( + viewModel = koinViewModel( + parameters = { parametersOf(sourceKey) } + ), + buildInfoProvider = koinInject() + ) + } + + composable { + ConfigurationLoaderScreen() + } + + homeScreenNavGraph() + + // Full Gallery screen (for Immich-style shared element transitions) + composable { + CompositionLocalProvider(LocalAnimatedVisibilityScope provides this) { + GalleryScreen() + } + } + + // Gallery Detail - transparent transitions for Immich-style overlay effect + // The gallery stays visible underneath during navigation + composable( + enterTransition = { fadeIn(animationSpec = tween(150)) }, + exitTransition = { fadeOut(animationSpec = tween(150)) }, + popEnterTransition = { fadeIn(animationSpec = tween(150)) }, + popExitTransition = { fadeOut(animationSpec = tween(200)) }, + ) { entry -> + // Provide AnimatedVisibilityScope for shared element transitions + CompositionLocalProvider(LocalAnimatedVisibilityScope provides this) { + val itemId = entry.toRoute().itemId + GalleryDetailScreen(itemId = itemId) + } + } + + composable( + enterTransition = { + fadeIn(animationSpec = tween(300)) + }, + exitTransition = { + fadeOut(animationSpec = tween(300)) + }, + ) { entry -> + val itemId = entry.toRoute().itemId + ImageEditorScreen(itemId = itemId) + } + + composable { entry -> + val itemId = entry.toRoute().itemId + ReportScreen( + viewModel = koinViewModel( + parameters = { parametersOf(itemId) } + ), + ) + } + + composable { + DebugMenuScreen() + } + + composable { + LoggerScreen() + } + + composable { + InPaintScreen() + } + + composable { + WebUiScreen() + } + + composable { + DonateScreen() + } + + composable( + typeMap = mapOf( + typeOf() to NavType.EnumType(LaunchSource::class.java) + ) + ) { entry -> + val sourceKey = entry.toRoute().source.ordinal + OnBoardingScreen( + viewModel = koinViewModel( + parameters = { parametersOf(sourceKey) } + ), + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/Router.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/Router.kt new file mode 100644 index 000000000..c21f746eb --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/Router.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.presentation.navigation.router + +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import io.reactivex.rxjava3.core.Observable + +interface Router { + fun observe(): Observable +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouter.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouter.kt new file mode 100644 index 000000000..88e1174ea --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouter.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.presentation.navigation.router.drawer + +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.router.Router + +interface DrawerRouter : Router { + + fun openDrawer() + + fun closeDrawer() +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouterImpl.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouterImpl.kt similarity index 76% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouterImpl.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouterImpl.kt index 94f7372c6..ce3a7e44d 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouterImpl.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouterImpl.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.drawer +package dev.minios.pdaiv1.presentation.navigation.router.drawer -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect import io.reactivex.rxjava3.subjects.PublishSubject internal class DrawerRouterImpl : DrawerRouter { diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/home/HomeRouter.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/home/HomeRouter.kt new file mode 100644 index 000000000..3c4b0adcb --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/home/HomeRouter.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.presentation.navigation.router.home + +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.navigation.router.Router + +interface HomeRouter : Router { + + fun updateExternallyWithoutNavigation(navRoute: NavigationRoute) + + fun navigateToRoute(navRoute: NavigationRoute) + + fun navigateToTxt2Img() + + fun navigateToImg2Img() + + fun navigateToGallery() + + fun navigateToSettings() +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/home/HomeRouterImpl.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/home/HomeRouterImpl.kt similarity index 83% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/home/HomeRouterImpl.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/home/HomeRouterImpl.kt index 6535a888a..d82a2690b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/home/HomeRouterImpl.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/home/HomeRouterImpl.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.home +package dev.minios.pdaiv1.presentation.navigation.router.home -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.PublishSubject diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouter.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouter.kt new file mode 100644 index 000000000..ae8eb388d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouter.kt @@ -0,0 +1,34 @@ +package dev.minios.pdaiv1.presentation.navigation.router.main + +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.router.Router + +interface MainRouter : Router { + + fun navigateBack() + + fun navigateToOnBoarding(source: LaunchSource) + + fun navigateToPostSplashConfigLoader() + + fun navigateToHomeScreen() + + fun navigateToServerSetup(source: LaunchSource) + + fun navigateToGalleryDetails(itemId: Long) + + fun navigateToGalleryFull() + + fun navigateToImageEditor(itemId: Long) + + fun navigateToReportImage(itemId: Long) + + fun navigateToInPaint() + + fun navigateToDonate() + + fun navigateToDebugMenu() + + fun navigateToLogger() +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterExtensions.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterExtensions.kt new file mode 100644 index 000000000..6debf6a13 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterExtensions.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.presentation.navigation.router.main + +import dev.minios.pdaiv1.domain.usecase.splash.SplashNavigationUseCase +import dev.minios.pdaiv1.presentation.model.LaunchSource + +fun MainRouter.postSplashNavigation( + action: SplashNavigationUseCase.Action, +) { + when (action) { + SplashNavigationUseCase.Action.LAUNCH_ONBOARDING -> navigateToOnBoarding( + source = LaunchSource.SPLASH, + ) + SplashNavigationUseCase.Action.LAUNCH_SERVER_SETUP -> navigateToServerSetup( + source = LaunchSource.SPLASH, + ) + SplashNavigationUseCase.Action.LAUNCH_HOME -> navigateToPostSplashConfigLoader() + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterImpl.kt similarity index 80% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterImpl.kt index 2a10039a0..3958a73e3 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterImpl.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.main +package dev.minios.pdaiv1.presentation.navigation.router.main -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.PublishSubject @@ -67,6 +67,22 @@ internal class MainRouterImpl : MainRouter { ) } + override fun navigateToImageEditor(itemId: Long) { + effectSubject.onNext( + NavigationEffect.Navigate.Route( + navRoute = NavigationRoute.ImageEditor(itemId = itemId) + ) + ) + } + + override fun navigateToGalleryFull() { + effectSubject.onNext( + NavigationEffect.Navigate.Route( + navRoute = NavigationRoute.GalleryFull + ) + ) + } + override fun navigateToReportImage(itemId: Long) { effectSubject.onNext( NavigationEffect.Navigate.Route( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuAccessor.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuAccessor.kt new file mode 100644 index 000000000..018cf122d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuAccessor.kt @@ -0,0 +1,24 @@ +package dev.minios.pdaiv1.presentation.screen.debug + +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.presentation.utils.Constants + +class DebugMenuAccessor( + private val preferenceManager: PreferenceManager, +) { + + private var tapCount = 0; + + operator fun invoke(): Boolean { + if (preferenceManager.developerMode) { + return true + } + tapCount++ + if (tapCount >= Constants.DEBUG_MENU_ACCESS_TAPS) { + tapCount = 0 + preferenceManager.developerMode = true + return true + } + return false + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuEffect.kt new file mode 100644 index 000000000..b6aada009 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuEffect.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.presentation.screen.debug + +import dev.minios.pdaiv1.core.model.UiText +import com.shifthackz.android.core.mvi.MviEffect + +sealed interface DebugMenuEffect : MviEffect { + data class Message(val message: UiText) : DebugMenuEffect +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuIntent.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuIntent.kt index dae80520d..0fcd623e9 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuIntent.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.presentation.screen.debug +package dev.minios.pdaiv1.presentation.screen.debug -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken import com.shifthackz.android.core.mvi.MviIntent sealed interface DebugMenuIntent : MviIntent { diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuMappers.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuMappers.kt new file mode 100644 index 000000000..19ba65e37 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuMappers.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.presentation.screen.debug + +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +fun SchedulersToken.mapToUi(): UiText = when (this) { + SchedulersToken.MAIN_THREAD -> LocalizationR.string.scheduler_main + SchedulersToken.IO_THREAD -> LocalizationR.string.scheduler_io + SchedulersToken.COMPUTATION -> LocalizationR.string.scheduler_computation + SchedulersToken.SINGLE_THREAD -> LocalizationR.string.scheduler_single_thread +}.asUiText() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuScreen.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuScreen.kt index 7654b6c04..aec418a0b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.screen.debug +package dev.minios.pdaiv1.presentation.screen.debug import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -31,14 +31,14 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.extensions.showToast -import com.shifthackz.aisdv1.core.model.asUiText +import dev.minios.pdaiv1.core.common.extensions.showToast +import dev.minios.pdaiv1.core.model.asUiText import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.widget.item.SettingsHeader -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.widget.item.SettingsHeader +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun DebugMenuScreen() { diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuState.kt new file mode 100644 index 000000000..9b79e1c5f --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuState.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.presentation.screen.debug + +import dev.minios.pdaiv1.core.common.schedulers.SchedulersToken +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviState + +data class DebugMenuState( + val screenModal: Modal = Modal.None, + val localDiffusionAllowCancel: Boolean = false, + val localDiffusionSchedulerThread: SchedulersToken = SchedulersToken.COMPUTATION, +) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuViewModel.kt similarity index 77% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuViewModel.kt index c6b5f7de4..9b5a95b15 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuViewModel.kt @@ -1,20 +1,20 @@ -package com.shifthackz.aisdv1.presentation.screen.debug - -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.log.FileLoggingTree -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter +package dev.minios.pdaiv1.presentation.screen.debug + +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.FileLoggingTree +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.debug.DebugInsertBadBase64UseCase +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter import io.reactivex.rxjava3.kotlin.subscribeBy -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR class DebugMenuViewModel( dispatchersProvider: DispatchersProvider, diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateEffect.kt new file mode 100644 index 000000000..28c6b0842 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateEffect.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.presentation.screen.donate + +import com.shifthackz.android.core.mvi.MviEffect + +sealed interface DonateEffect : MviEffect { + data class OpenUrl(val url: String) : DonateEffect +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateIntent.kt similarity index 81% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateIntent.kt index 72ecbf26f..7cc1d2a51 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateIntent.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.presentation.screen.donate +package dev.minios.pdaiv1.presentation.screen.donate -import com.shifthackz.aisdv1.core.common.links.LinksProvider +import dev.minios.pdaiv1.core.common.links.LinksProvider import com.shifthackz.android.core.mvi.MviIntent import org.koin.core.component.KoinComponent import org.koin.core.component.inject diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateScreen.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateScreen.kt index 732baf225..a88776e54 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.screen.donate +package dev.minios.pdaiv1.presentation.screen.donate import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.Image @@ -46,15 +46,15 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.common.extensions.openUrl -import com.shifthackz.aisdv1.core.extensions.fadedEdge -import com.shifthackz.aisdv1.domain.entity.Supporter -import com.shifthackz.aisdv1.presentation.widget.item.SupporterItem +import dev.minios.pdaiv1.core.common.extensions.openUrl +import dev.minios.pdaiv1.core.extensions.fadedEdge +import dev.minios.pdaiv1.domain.entity.Supporter +import dev.minios.pdaiv1.presentation.widget.item.SupporterItem import com.shifthackz.android.core.mvi.MviComponent import org.koin.androidx.compose.koinViewModel import java.util.Date -import com.shifthackz.aisdv1.core.localization.R as LocalizationR -import com.shifthackz.aisdv1.presentation.R as PresentationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.R as PresentationR @Composable fun DonateScreen() { @@ -125,8 +125,8 @@ private fun DonateScreenContent( ) { Image( modifier = Modifier.size(36.dp), - painter = painterResource(id = PresentationR.drawable.ic_sdai_logo), - contentDescription = "SDAI Android Branding", + painter = painterResource(id = PresentationR.drawable.ic_pdai_logo), + contentDescription = "PDAI Android Branding", ) Text( text = stringResource(id = LocalizationR.string.donate_bs_title), @@ -256,13 +256,13 @@ private fun StateContentPreview() { id = 0, name = "ShiftHackZ", date = Date(), - message = "Sdai", + message = "Pdai", ), Supporter( id = 1, name = "ShiftHackZ", date = Date(), - message = "Sdai", + message = "Pdai", ), ), ), diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateState.kt new file mode 100644 index 000000000..15dd5b93b --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateState.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.presentation.screen.donate + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.domain.entity.Supporter +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class DonateState( + val loading: Boolean = true, + val supporters: List = emptyList(), +) : MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateViewModel.kt new file mode 100644 index 000000000..05acc8f12 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/donate/DonateViewModel.kt @@ -0,0 +1,48 @@ +package dev.minios.pdaiv1.presentation.screen.donate + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.usecase.donate.FetchAndGetSupportersUseCase +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import io.reactivex.rxjava3.kotlin.subscribeBy + +class DonateViewModel( + dispatchersProvider: DispatchersProvider, + schedulersProvider: SchedulersProvider, + fetchAndGetSupportersUseCase: FetchAndGetSupportersUseCase, + private val mainRouter: MainRouter, +) : MviRxViewModel() { + + override val initialState = DonateState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !fetchAndGetSupportersUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + updateState { it.copy(loading = false) } + errorLog(t) + }, + onSuccess = { supporters -> + updateState { + it.copy( + loading = false, + supporters = supporters, + ) + } + }, + ) + } + + override fun processIntent(intent: DonateIntent) { + when (intent) { + is DonateIntent.LaunchUrl -> emitEffect(DonateEffect.OpenUrl(intent.url)) + DonateIntent.NavigateBack -> mainRouter.navigateBack() + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerIntent.kt similarity index 75% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerIntent.kt index cc8244fdd..eb74dac71 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerIntent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.drawer +package dev.minios.pdaiv1.presentation.screen.drawer import com.shifthackz.android.core.mvi.MviIntent diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerScreen.kt similarity index 80% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerScreen.kt index 0371b910f..8d8d4b7f9 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerScreen.kt @@ -1,11 +1,14 @@ -package com.shifthackz.aisdv1.presentation.screen.drawer +package dev.minios.pdaiv1.presentation.screen.drawer import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Android @@ -28,14 +31,15 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavBackStackEntry import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.model.asString +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.model.asString import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.presentation.R -import com.shifthackz.aisdv1.presentation.model.NavItem -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.aisdv1.presentation.widget.item.NavigationItemIcon +import dev.minios.pdaiv1.presentation.R +import dev.minios.pdaiv1.presentation.model.NavItem +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.widget.item.NavigationItemIcon +import dev.minios.pdaiv1.presentation.widget.work.BackgroundWorkWidget import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject @@ -63,11 +67,7 @@ fun DrawerScreen( viewModel = koinViewModel(), ) { _, intentHandler -> ModalNavigationDrawer( - gesturesEnabled = if (drawerState.isOpen) { - true - } else { - currentRootRoute?.hasRoute(NavigationRoute.Home::class) == true - }, + gesturesEnabled = drawerState.isOpen, // Only allow gesture to close, not to open drawerState = drawerState, drawerContent = { ModalDrawerSheet( @@ -80,8 +80,8 @@ fun DrawerScreen( verticalAlignment = Alignment.CenterVertically, ) { Image( - painter = painterResource(id = R.drawable.ic_sdai_logo), - contentDescription = "SDAI Android Branding", + painter = painterResource(id = R.drawable.ic_pdai_logo), + contentDescription = "PDAI Android Branding", ) Column( modifier = Modifier.padding(start = 12.dp) @@ -91,7 +91,7 @@ fun DrawerScreen( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = "SDAI", + text = "PDAI", style = MaterialTheme.typography.headlineMedium, ) Icon( @@ -141,7 +141,17 @@ fun DrawerScreen( } }, ) { - content() + Box(modifier = Modifier.fillMaxSize()) { + content() + + // Global floating BackgroundWorkWidget - inside drawer content so drawer is on top + BackgroundWorkWidget( + modifier = Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .padding(bottom = 88.dp), // Above bottom navigation with extra spacing for swipe + ) + } } } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerViewModel.kt similarity index 75% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerViewModel.kt index 9c9ca080b..b59f3394d 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerViewModel.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.presentation.screen.drawer +package dev.minios.pdaiv1.presentation.screen.drawer -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter import com.shifthackz.android.core.mvi.EmptyEffect import com.shifthackz.android.core.mvi.EmptyState import com.shifthackz.android.core.mvi.MviViewModel diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationEffect.kt new file mode 100644 index 000000000..04406df71 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationEffect.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.presentation.screen.falai + +import com.shifthackz.android.core.mvi.MviEffect + +sealed interface FalAiGenerationEffect : MviEffect diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationIntent.kt new file mode 100644 index 000000000..2a3560ba4 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationIntent.kt @@ -0,0 +1,26 @@ +package dev.minios.pdaiv1.presentation.screen.falai + +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface FalAiGenerationIntent : MviIntent { + + data class SelectEndpoint(val endpointId: String) : FalAiGenerationIntent + + data class UpdateProperty(val name: String, val value: Any?) : FalAiGenerationIntent + + data class ToggleAdvancedOptions(val visible: Boolean) : FalAiGenerationIntent + + data object Generate : FalAiGenerationIntent + + data class SetModal(val modal: Modal) : FalAiGenerationIntent + + data object DismissModal : FalAiGenerationIntent + + data class Drawer(val intent: DrawerIntent) : FalAiGenerationIntent + + data class ImportOpenApiJson(val json: String) : FalAiGenerationIntent + + data class DeleteEndpoint(val endpointId: String) : FalAiGenerationIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationScreen.kt new file mode 100644 index 000000000..fe29737d3 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationScreen.kt @@ -0,0 +1,181 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package dev.minios.pdaiv1.presentation.screen.falai + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.Button +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.shifthackz.android.core.mvi.MviComponent +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.widget.falai.FalAiDynamicForm +import dev.minios.pdaiv1.presentation.widget.scaffold.CollapsibleScaffold +import org.koin.androidx.compose.koinViewModel +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun FalAiGenerationScreen() { + MviComponent( + viewModel = koinViewModel(), + ) { state, intentHandler -> + FalAiGenerationScreenContent( + modifier = Modifier.fillMaxSize(), + state = state, + processIntent = intentHandler, + ) + } +} + +@Composable +fun FalAiGenerationScreenContent( + modifier: Modifier = Modifier, + state: FalAiGenerationState, + processIntent: (FalAiGenerationIntent) -> Unit = {}, +) { + val keyboardController = LocalSoftwareKeyboardController.current + val scrollState = rememberScrollState() + + Box(modifier) { + CollapsibleScaffold( + scrollState = scrollState, + bottomToolbarHeight = 0.dp, + topBarContent = { + CenterAlignedTopAppBar( + navigationIcon = { + IconButton(onClick = { + processIntent(FalAiGenerationIntent.Drawer(DrawerIntent.Open)) + }) { + Icon( + imageVector = Icons.Default.Menu, + contentDescription = "Menu", + ) + } + }, + title = { + Text( + text = "Fal AI", + style = MaterialTheme.typography.headlineMedium, + ) + }, + windowInsets = WindowInsets(0, 0, 0, 0), + ) + }, + contentScrollable = { + when { + state.loading -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 64.dp), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator() + } + } + + state.endpoints.isEmpty() -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 64.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = "No endpoints available", + style = MaterialTheme.typography.bodyLarge, + ) + } + } + + else -> { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + Spacer(modifier = Modifier.height(8.dp)) + + FalAiDynamicForm( + state = state, + onEndpointSelected = { id -> + processIntent(FalAiGenerationIntent.SelectEndpoint(id)) + }, + onPropertyChanged = { name, value -> + processIntent(FalAiGenerationIntent.UpdateProperty(name, value)) + }, + onToggleAdvanced = { visible -> + processIntent(FalAiGenerationIntent.ToggleAdvancedOptions(visible)) + }, + onImportOpenApi = { json -> + processIntent(FalAiGenerationIntent.ImportOpenApiJson(json)) + }, + onDeleteEndpoint = { id -> + processIntent(FalAiGenerationIntent.DeleteEndpoint(id)) + }, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Generate button + Button( + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + enabled = state.canGenerate, + onClick = { + keyboardController?.hide() + processIntent(FalAiGenerationIntent.Generate) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.action_generate), + color = LocalContentColor.current, + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + } + } + } + }, + ) + + // Modal rendering + ModalRenderer( + screenModal = state.screenModal, + processIntent = { intent -> + when (intent) { + is FalAiGenerationIntent -> processIntent(intent) + else -> { + // Handle dismiss - check if it's a dismiss intent + if (state.screenModal != Modal.None) { + processIntent(FalAiGenerationIntent.DismissModal) + } + } + } + }, + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationState.kt new file mode 100644 index 000000000..6ef039069 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationState.kt @@ -0,0 +1,28 @@ +package dev.minios.pdaiv1.presentation.screen.falai + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.presentation.model.FalAiEndpointUi +import dev.minios.pdaiv1.presentation.model.FalAiPropertyUi +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class FalAiGenerationState( + val loading: Boolean = true, + val screenModal: Modal = Modal.None, + val endpoints: List = emptyList(), + val selectedEndpoint: FalAiEndpointUi? = null, + val propertyValues: Map = emptyMap(), + val advancedOptionsVisible: Boolean = false, + val generating: Boolean = false, + val generationError: String? = null, + val mainProperties: List = emptyList(), + val advancedProperties: List = emptyList(), +) : MviState { + + val hasAdvancedProperties: Boolean + get() = advancedProperties.isNotEmpty() + + val canGenerate: Boolean + get() = selectedEndpoint != null && !generating && propertyValues["prompt"]?.toString()?.isNotBlank() == true +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationViewModel.kt new file mode 100644 index 000000000..8c20f0d55 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/falai/FalAiGenerationViewModel.kt @@ -0,0 +1,439 @@ +package dev.minios.pdaiv1.presentation.screen.falai + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.FalAiEndpoint +import dev.minios.pdaiv1.domain.entity.FalAiPayload +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActor +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import dev.minios.pdaiv1.domain.repository.FalAiGenerationRepository +import dev.minios.pdaiv1.domain.usecase.generation.SaveGenerationResultUseCase +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.model.toUi +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import io.reactivex.rxjava3.kotlin.subscribeBy +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +class FalAiGenerationViewModel( + dispatchersProvider: DispatchersProvider, + private val falAiEndpointRepository: FalAiEndpointRepository, + private val falAiGenerationRepository: FalAiGenerationRepository, + private val saveGenerationResultUseCase: SaveGenerationResultUseCase, + private val schedulersProvider: SchedulersProvider, + private val preferenceManager: PreferenceManager, + private val wakeLockInterActor: WakeLockInterActor, + private val notificationManager: PushNotificationManager, + private val drawerRouter: DrawerRouter, + private val buildInfoProvider: BuildInfoProvider, + private val backgroundTaskManager: BackgroundTaskManager, + private val backgroundWorkObserver: BackgroundWorkObserver, + private val generationFormUpdateEvent: GenerationFormUpdateEvent, +) : MviRxViewModel() { + + override val initialState = FalAiGenerationState() + + override val effectDispatcher = dispatchersProvider.immediate + + private var selectedEndpointDomain: FalAiEndpoint? = null + + init { + loadEndpoints() + observeFormUpdates() + } + + private fun observeFormUpdates() { + !generationFormUpdateEvent.observeFalAiForm() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onNext = { payload -> + when (payload) { + is GenerationFormUpdateEvent.Payload.FalAiForm -> { + applyFormFromGallery(payload.ai) + } + else -> Unit + } + }, + ) + } + + private fun applyFormFromGallery(ai: AiGenerationResult) { + // Extract endpoint ID from sampler field (format: "fal.ai/endpoint-id") + val endpointId = ai.sampler.removePrefix("fal.ai/") + + // First select the endpoint, then apply saved values on top + !falAiEndpointRepository.getAll() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onSuccess = { endpoints -> + val endpoint = endpoints.find { it.id == endpointId || it.endpointId == endpointId } + + // Use found endpoint or fall back to currently selected endpoint + val targetEndpoint = endpoint ?: selectedEndpointDomain + + if (targetEndpoint != null) { + if (endpoint != null) { + selectedEndpointDomain = endpoint + preferenceManager.falAiSelectedEndpointId = endpoint.id + } + + val endpointUi = targetEndpoint.toUi() + val defaultValues = targetEndpoint.schema.inputProperties + .associate { prop -> prop.name to prop.default } + .toMutableMap() + + // Apply saved values from gallery + defaultValues["prompt"] = ai.prompt + if (ai.negativePrompt.isNotBlank()) { + defaultValues["negative_prompt"] = ai.negativePrompt + } + if (ai.seed.isNotBlank()) { + defaultValues["seed"] = ai.seed.toLongOrNull() + } + + // Apply image_size if endpoint supports custom dimensions + val imageSizeProperty = targetEndpoint.schema.inputProperties.find { it.name == "image_size" } + if (imageSizeProperty != null && imageSizeProperty.type == FalAiPropertyType.IMAGE_SIZE) { + defaultValues["image_size"] = mapOf("width" to ai.width, "height" to ai.height) + } + + val allProperties = endpointUi.properties.map { prop -> + val savedValue = defaultValues[prop.name] + prop.copy(currentValue = savedValue ?: prop.defaultValue) + } + + val mainProps = allProperties.filter { !it.isAdvanced } + val advancedProps = allProperties.filter { it.isAdvanced } + + updateState { state -> + state.copy( + selectedEndpoint = endpointUi, + propertyValues = defaultValues, + mainProperties = mainProps, + advancedProperties = advancedProps, + ) + } + } else { + // No endpoint available, just update prompt in current state + updateState { state -> + val updatedValues = state.propertyValues.toMutableMap() + updatedValues["prompt"] = ai.prompt + if (ai.negativePrompt.isNotBlank()) { + updatedValues["negative_prompt"] = ai.negativePrompt + } + state.copy(propertyValues = updatedValues) + } + } + + // Clear the form update event + generationFormUpdateEvent.clear() + }, + ) + } + + override fun processIntent(intent: FalAiGenerationIntent) { + when (intent) { + is FalAiGenerationIntent.SelectEndpoint -> selectEndpoint(intent.endpointId) + is FalAiGenerationIntent.UpdateProperty -> updateProperty(intent.name, intent.value) + is FalAiGenerationIntent.ToggleAdvancedOptions -> toggleAdvancedOptions(intent.visible) + is FalAiGenerationIntent.Generate -> generate() + is FalAiGenerationIntent.SetModal -> setModal(intent.modal) + is FalAiGenerationIntent.DismissModal -> dismissModal() + is FalAiGenerationIntent.Drawer -> when (intent.intent) { + DrawerIntent.Open -> drawerRouter.openDrawer() + DrawerIntent.Close -> drawerRouter.closeDrawer() + } + is FalAiGenerationIntent.ImportOpenApiJson -> importOpenApiJson(intent.json) + is FalAiGenerationIntent.DeleteEndpoint -> deleteEndpoint(intent.endpointId) + } + } + + private fun loadEndpoints() { + !falAiEndpointRepository.getAll() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + errorLog(t) + updateState { it.copy(loading = false) } + }, + onSuccess = { endpoints -> + val uiEndpoints = endpoints.map { it.toUi() } + val selectedId = preferenceManager.falAiSelectedEndpointId + val selected = endpoints.find { it.id == selectedId } + ?: endpoints.find { it.endpointId == selectedId } + ?: endpoints.firstOrNull() + + selectedEndpointDomain = selected + + val selectedUi = selected?.toUi() + + // Start with default values + val defaultValues = selected?.schema?.inputProperties + ?.associate { prop -> prop.name to prop.default } + ?.toMutableMap() + ?: mutableMapOf() + + // Overlay saved params on top of defaults + if (selected != null) { + val savedParams = preferenceManager.getFalAiEndpointParams(selected.endpointId) + savedParams.forEach { (key, value) -> + if (value != null) { + defaultValues[key] = value + } + } + } + + val allProperties = selectedUi?.properties?.map { prop -> + prop.copy(currentValue = defaultValues[prop.name] ?: prop.defaultValue) + } ?: emptyList() + + val mainProps = allProperties.filter { !it.isAdvanced } + val advancedProps = allProperties.filter { it.isAdvanced } + + updateState { state -> + state.copy( + loading = false, + endpoints = uiEndpoints, + selectedEndpoint = selectedUi, + propertyValues = defaultValues, + mainProperties = mainProps, + advancedProperties = advancedProps, + ) + } + }, + ) + } + + private fun selectEndpoint(endpointId: String) { + !falAiEndpointRepository.getAll() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onSuccess = { endpoints -> + val endpoint = endpoints.find { it.id == endpointId || it.endpointId == endpointId } + if (endpoint != null) { + selectedEndpointDomain = endpoint + preferenceManager.falAiSelectedEndpointId = endpoint.id + + val endpointUi = endpoint.toUi() + + // Start with default values + val defaultValues = endpoint.schema.inputProperties + .associate { prop -> prop.name to prop.default } + .toMutableMap() + + // Overlay saved params on top of defaults + val savedParams = preferenceManager.getFalAiEndpointParams(endpoint.endpointId) + savedParams.forEach { (key, value) -> + if (value != null) { + defaultValues[key] = value + } + } + + val allProperties = endpointUi.properties.map { prop -> + prop.copy(currentValue = defaultValues[prop.name] ?: prop.defaultValue) + } + + val mainProps = allProperties.filter { !it.isAdvanced } + val advancedProps = allProperties.filter { it.isAdvanced } + + updateState { state -> + state.copy( + selectedEndpoint = endpointUi, + propertyValues = defaultValues, + mainProperties = mainProps, + advancedProperties = advancedProps, + ) + } + } + }, + ) + } + + private fun updateProperty(name: String, value: Any?) { + updateState { state -> + val newPropertyValues = state.propertyValues.toMutableMap().apply { + put(name, value) + } + + val updatedMainProperties = state.mainProperties.map { prop -> + if (prop.name == name) prop.copy(currentValue = value) else prop + } + + val updatedAdvancedProperties = state.advancedProperties.map { prop -> + if (prop.name == name) prop.copy(currentValue = value) else prop + } + + state.copy( + propertyValues = newPropertyValues, + mainProperties = updatedMainProperties, + advancedProperties = updatedAdvancedProperties, + ) + } + } + + private fun toggleAdvancedOptions(visible: Boolean) { + updateState { it.copy(advancedOptionsVisible = visible) } + } + + private fun setModal(modal: Modal) { + updateState { it.copy(screenModal = modal) } + } + + private fun dismissModal() { + updateState { it.copy(screenModal = Modal.None) } + } + + private fun importOpenApiJson(json: String) { + !falAiEndpointRepository.importFromJson(json) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + errorLog(t) + updateState { state -> + state.copy( + screenModal = Modal.Error( + (t.localizedMessage ?: "Failed to import endpoint").asUiText() + ) + ) + } + }, + onSuccess = { endpoint -> + // Reload endpoints and select the new one + loadEndpoints() + selectEndpoint(endpoint.id) + }, + ) + } + + private fun deleteEndpoint(endpointId: String) { + !falAiEndpointRepository.delete(endpointId) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + errorLog(t) + updateState { state -> + state.copy( + screenModal = Modal.Error( + (t.localizedMessage ?: "Failed to delete endpoint").asUiText() + ) + ) + } + }, + onComplete = { + loadEndpoints() + }, + ) + } + + private fun generate() { + val endpoint = selectedEndpointDomain ?: return + val parameters = currentState.propertyValues + + // Save parameters for this endpoint + preferenceManager.saveFalAiEndpointParams(endpoint.endpointId, parameters) + + if (backgroundWorkObserver.hasActiveTasks()) { + updateState { it.copy(screenModal = Modal.Background.Running) } + return + } + + if (preferenceManager.backgroundGeneration) { + generateBackground(endpoint.endpointId, parameters) + return + } + + generateOnUi(endpoint, parameters) + } + + private fun generateBackground(endpointId: String, parameters: Map) { + val payload = FalAiPayload( + endpointId = endpointId, + parameters = parameters, + ) + backgroundTaskManager.scheduleFalAiTask(payload) + backgroundWorkObserver.refreshStatus() + updateState { it.copy(screenModal = Modal.Background.Scheduled) } + } + + private fun generateOnUi(endpoint: FalAiEndpoint, parameters: Map) { + !falAiGenerationRepository.generateDynamic(endpoint, parameters) + .doOnSubscribe { + wakeLockInterActor.acquireWakelockUseCase() + updateState { it.copy(generating = true, screenModal = Modal.Communicating()) } + } + .doFinally { + wakeLockInterActor.releaseWakeLockUseCase() + } + .flatMapCompletable { results -> + if (preferenceManager.autoSaveAiResults) { + val saveCompletables = results.map { saveGenerationResultUseCase(it) } + io.reactivex.rxjava3.core.Completable.merge(saveCompletables) + .doOnComplete { + updateState { state -> + state.copy( + generating = false, + screenModal = Modal.Image.create( + list = results, + autoSaveEnabled = true, + reportEnabled = buildInfoProvider.type == BuildType.PLAY, + ), + ) + } + } + } else { + io.reactivex.rxjava3.core.Completable.fromAction { + updateState { state -> + state.copy( + generating = false, + screenModal = Modal.Image.create( + list = results, + autoSaveEnabled = false, + reportEnabled = buildInfoProvider.type == BuildType.PLAY, + ), + ) + } + } + } + } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + errorLog(t) + notificationManager.createAndShowInstant( + LocalizationR.string.notification_fail_title.asUiText(), + LocalizationR.string.notification_fail_sub_title.asUiText(), + ) + updateState { state -> + state.copy( + generating = false, + screenModal = Modal.Error( + (t.localizedMessage ?: "Generation failed").asUiText() + ), + ) + } + }, + onComplete = { + notificationManager.createAndShowInstant( + LocalizationR.string.notification_finish_title.asUiText(), + LocalizationR.string.notification_finish_sub_title.asUiText(), + ) + }, + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailBitmapExporter.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailBitmapExporter.kt new file mode 100644 index 000000000..7aba67e55 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailBitmapExporter.kt @@ -0,0 +1,17 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.detail + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.presentation.utils.FileSavableExporter +import io.reactivex.rxjava3.core.Single +import java.io.File + +class GalleryDetailBitmapExporter( + override val fileProviderDescriptor: FileProviderDescriptor, +) : FileSavableExporter.BmpToFile { + + operator fun invoke(bitmap: Bitmap): Single = saveBitmapToFile( + System.currentTimeMillis().toString(), + bitmap + ) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailEffect.kt new file mode 100644 index 000000000..c52ae4338 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailEffect.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.detail + +import com.shifthackz.android.core.mvi.MviEffect +import java.io.File + +sealed interface GalleryDetailEffect : MviEffect { + + data class ShareImageFile(val file: File) : GalleryDetailEffect + + data class ShareGenerationParams(val state: GalleryDetailState) : GalleryDetailEffect + + data class ShareClipBoard(val text: String) : GalleryDetailEffect + + data object ImageSavedToGallery : GalleryDetailEffect +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt new file mode 100644 index 000000000..139306e29 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt @@ -0,0 +1,44 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.detail + +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface GalleryDetailIntent : MviIntent { + + data object NavigateBack : GalleryDetailIntent + + data class SelectTab(val tab: GalleryDetailState.Tab) : GalleryDetailIntent + + data class CopyToClipboard(val content: CharSequence) : GalleryDetailIntent + + enum class SendTo : GalleryDetailIntent { + Img2Img, Txt2Img, FalAi; + } + + enum class Export : GalleryDetailIntent { + Image, Params; + } + + enum class Delete : GalleryDetailIntent { + Request, Confirm + } + + data object ToggleVisibility : GalleryDetailIntent + + data object ToggleLike : GalleryDetailIntent + + data object ToggleControlsVisibility : GalleryDetailIntent + + data object ShowInfoBottomSheet : GalleryDetailIntent + + data object HideInfoBottomSheet : GalleryDetailIntent + + data object OpenEditor : GalleryDetailIntent + + data object SaveToGallery : GalleryDetailIntent + + data object Report : GalleryDetailIntent + + data object DismissDialog : GalleryDetailIntent + + data class PageChanged(val index: Int) : GalleryDetailIntent +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt similarity index 81% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt index 96f47051d..c9e0e2b6a 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.detail +package dev.minios.pdaiv1.presentation.screen.gallery.detail import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.AiGenerationResult val mockGalleryDetailTxt2Img = GalleryDetailState.Content( tabs = listOf( @@ -28,4 +28,5 @@ val mockGalleryDetailTxt2Img = GalleryDetailState.Content( subSeedStrength = "".asUiText(), denoisingStrength = "".asUiText(), hidden = false, + modelName = "MockModel".asUiText(), ) diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt new file mode 100644 index 000000000..b6b1dd43f --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt @@ -0,0 +1,983 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class) + +package dev.minios.pdaiv1.presentation.screen.gallery.detail + +import android.view.HapticFeedbackConstants +import android.widget.Toast +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.gestures.detectVerticalDragGestures +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerDefaults +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView +import kotlinx.coroutines.launch +import kotlin.math.abs +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.ContentCopy +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FavoriteBorder +import androidx.compose.material.icons.filled.Report +import androidx.compose.material.icons.filled.Save +import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.foundation.layout.height +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.key +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.asComposeRenderEffect +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.sharing.shareFile +import com.shifthackz.android.core.mvi.MviComponent +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.theme.colors +import dev.minios.pdaiv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.widget.image.ZoomableImage +import dev.minios.pdaiv1.presentation.widget.image.ZoomableImageSource +import com.shifthackz.catppuccin.palette.Catppuccin +import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject +import org.koin.core.parameter.parametersOf +import dev.minios.pdaiv1.presentation.navigation.LocalAnimatedVisibilityScope +import dev.minios.pdaiv1.presentation.navigation.LocalSharedTransitionScope +import dev.minios.pdaiv1.presentation.navigation.galleryImageSharedKey +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.R as PresentationR + +@Composable +fun GalleryDetailScreen( + itemId: Long, + onNavigateBack: (() -> Unit)? = null, +) { + val context = LocalContext.current + val clipboardManager = LocalClipboardManager.current + val fileProviderDescriptor: FileProviderDescriptor = koinInject() + val galleryDetailSharing: GalleryDetailSharing = koinInject() + MviComponent( + viewModel = koinViewModel( + key = "gallery_detail_$itemId", + parameters = { parametersOf(itemId, onNavigateBack) }, + ), + processEffect = { effect -> + when (effect) { + is GalleryDetailEffect.ShareImageFile -> context.shareFile( + file = effect.file, + fileProviderPath = fileProviderDescriptor.providerPath, + fileMimeType = Constants.MIME_TYPE_JPG, + ) + + is GalleryDetailEffect.ShareGenerationParams -> galleryDetailSharing( + context = context, + state = effect.state, + ) + + is GalleryDetailEffect.ShareClipBoard -> { + clipboardManager.setText(AnnotatedString(effect.text)) + } + + GalleryDetailEffect.ImageSavedToGallery -> { + Toast.makeText( + context, + context.getString(LocalizationR.string.gallery_save_success), + Toast.LENGTH_SHORT, + ).show() + } + } + }, + ) { state, intentHandler -> + ScreenContent( + modifier = Modifier.fillMaxSize(), + state = state, + processIntent = intentHandler, + ) + } +} + +@Composable +private fun ScreenContent( + modifier: Modifier = Modifier, + state: GalleryDetailState, + processIntent: (GalleryDetailIntent) -> Unit = {}, +) { + val isImageTab = state.selectedTab == GalleryDetailState.Tab.IMAGE || + state.selectedTab == GalleryDetailState.Tab.ORIGINAL + val showControls = state.controlsVisible || !isImageTab + + // Track swipe progress for controls fade + var controlsAlpha by remember { mutableFloatStateOf(1f) } + + // No background here - GalleryDetailContentState handles its own animated background + // This allows the gallery grid to be visible underneath during swipe-to-dismiss + + Box( + modifier = modifier, + ) { + // Image content - fills entire screen when controls hidden + when (state) { + is GalleryDetailState.Content -> GalleryDetailContentState( + modifier = Modifier.fillMaxSize(), + state = state, + onCopyTextClick = { + processIntent(GalleryDetailIntent.CopyToClipboard(it)) + }, + onPageChanged = { page -> + processIntent(GalleryDetailIntent.PageChanged(page)) + }, + onImageTap = { + if (isImageTab) { + processIntent(GalleryDetailIntent.ToggleControlsVisibility) + } + }, + onSwipeDown = { + processIntent(GalleryDetailIntent.NavigateBack) + }, + onSwipeUp = { + processIntent(GalleryDetailIntent.ShowInfoBottomSheet) + }, + onDragProgressChanged = { alpha -> + controlsAlpha = alpha + }, + ) + + is GalleryDetailState.Loading -> Unit + } + + // Top bar overlay with gradient background for visibility on black + AnimatedVisibility( + visible = showControls, + enter = fadeIn(animationSpec = tween(200)) + + slideInVertically(animationSpec = tween(200)) { -it }, + exit = fadeOut(animationSpec = tween(200)) + + slideOutVertically(animationSpec = tween(200)) { -it }, + modifier = Modifier + .align(Alignment.TopCenter) + .graphicsLayer { alpha = controlsAlpha }, + ) { + var showDropdownMenu by remember { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxWidth() + .background( + if (isImageTab) { + Brush.verticalGradient( + colors = listOf( + Color.Black.copy(alpha = 0.7f), + Color.Black.copy(alpha = 0.5f), + Color.Black.copy(alpha = 0.3f), + Color.Transparent, + ) + ) + } else { + Brush.verticalGradient( + colors = listOf( + MaterialTheme.colorScheme.surface, + MaterialTheme.colorScheme.surface, + ) + ) + } + ) + .statusBarsPadding() + .padding(horizontal = 4.dp, vertical = 4.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + // Back button + IconButton( + onClick = { processIntent(GalleryDetailIntent.NavigateBack) }, + ) { + Icon( + Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = "Back button", + tint = if (isImageTab) Color.White else LocalContentColor.current, + ) + } + + // Menu button + AnimatedVisibility( + visible = state.selectedTab != GalleryDetailState.Tab.INFO, + enter = fadeIn(), + exit = fadeOut(), + ) { + Box { + IconButton( + onClick = { showDropdownMenu = true }, + ) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = "More options", + tint = if (isImageTab) Color.White else LocalContentColor.current, + ) + } + DropdownMenu( + expanded = showDropdownMenu, + onDismissRequest = { showDropdownMenu = false }, + modifier = Modifier.background(MaterialTheme.colorScheme.surface), + ) { + DropdownMenuItem( + text = { Text(stringResource(LocalizationR.string.gallery_save_to_gallery)) }, + onClick = { + showDropdownMenu = false + processIntent(GalleryDetailIntent.SaveToGallery) + }, + leadingIcon = { + Icon(Icons.Default.Save, contentDescription = null) + }, + ) + DropdownMenuItem( + text = { Text(stringResource(LocalizationR.string.gallery_info_field_prompt)) }, + onClick = { + showDropdownMenu = false + processIntent(GalleryDetailIntent.Export.Params) + }, + leadingIcon = { + Icon(Icons.Default.Share, contentDescription = null) + }, + ) + DropdownMenuItem( + text = { Text(stringResource(LocalizationR.string.gallery_tab_info)) }, + onClick = { + showDropdownMenu = false + processIntent(GalleryDetailIntent.ShowInfoBottomSheet) + }, + leadingIcon = { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(id = PresentationR.drawable.ic_text), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current), + ) + }, + ) + } + } + } + } + } + } + + // Bottom bar overlay with gradient for visibility on black + AnimatedVisibility( + visible = showControls, + enter = fadeIn(animationSpec = tween(200)) + + slideInVertically(animationSpec = tween(200)) { it }, + exit = fadeOut(animationSpec = tween(200)) + + slideOutVertically(animationSpec = tween(200)) { it }, + modifier = Modifier + .align(Alignment.BottomCenter) + .graphicsLayer { alpha = controlsAlpha }, + ) { + GalleryDetailNavigationBar( + state = state, + isImageTab = isImageTab, + processIntent = processIntent, + ) + } + + ModalRenderer(screenModal = state.screenModal) { + (it as? GalleryDetailIntent)?.let(processIntent::invoke) + } + + // Info Bottom Sheet (shown on swipe up) + if (state.showInfoBottomSheet && state is GalleryDetailState.Content) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false) + + ModalBottomSheet( + onDismissRequest = { + processIntent(GalleryDetailIntent.HideInfoBottomSheet) + }, + sheetState = sheetState, + containerColor = MaterialTheme.colorScheme.surface, + dragHandle = { + Box( + modifier = Modifier + .padding(vertical = 12.dp) + .size(width = 40.dp, height = 4.dp) + .background( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), + shape = RoundedCornerShape(2.dp) + ) + ) + }, + ) { + GalleryDetailsTable( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 32.dp), + state = state, + onCopyTextClick = { text -> + processIntent(GalleryDetailIntent.CopyToClipboard(text)) + }, + ) + } + } + } +} + +@Composable +private fun GalleryDetailNavigationBar( + state: GalleryDetailState, + isImageTab: Boolean, + processIntent: (GalleryDetailIntent) -> Unit = {}, +) { + val iconTint = if (isImageTab) Color.White else LocalContentColor.current + + Box( + modifier = Modifier + .fillMaxWidth() + .background( + if (isImageTab) { + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.3f), + Color.Black.copy(alpha = 0.7f), + ) + ) + } else { + Brush.verticalGradient( + colors = listOf( + MaterialTheme.colorScheme.surface, + MaterialTheme.colorScheme.surface, + ) + ) + } + ) + ) { + Column { + if (state is GalleryDetailState.Content) { + if (state.showReportButton) { + OutlinedButton( + modifier = Modifier + .padding(horizontal = 24.dp, vertical = 4.dp) + .fillMaxWidth() + .align(Alignment.CenterHorizontally), + onClick = { processIntent(GalleryDetailIntent.Report) }, + colors = androidx.compose.material3.ButtonDefaults.outlinedButtonColors( + contentColor = iconTint, + ), + border = androidx.compose.foundation.BorderStroke( + width = 1.dp, + color = iconTint.copy(alpha = 0.5f), + ), + ) { + Icon( + modifier = Modifier.padding(end = 8.dp), + imageVector = Icons.Default.Report, + contentDescription = "Report", + tint = iconTint, + ) + Text( + text = stringResource(LocalizationR.string.report_title), + color = iconTint, + ) + } + } + // Action buttons row + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + if (state.currentSource == ServerSource.FAL_AI) { + // Fal AI button + IconButton( + onClick = { processIntent(GalleryDetailIntent.SendTo.FalAi) }, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = PresentationR.drawable.ic_text), + contentDescription = "Fal AI", + tint = iconTint, + ) + } + } else { + // txt2img button + IconButton( + onClick = { processIntent(GalleryDetailIntent.SendTo.Txt2Img) }, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = PresentationR.drawable.ic_text), + contentDescription = "txt2img", + tint = iconTint, + ) + } + // img2img button + IconButton( + onClick = { processIntent(GalleryDetailIntent.SendTo.Img2Img) }, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = PresentationR.drawable.ic_image), + contentDescription = "img2img", + tint = iconTint, + ) + } + } + // Visibility toggle + IconButton( + onClick = { processIntent(GalleryDetailIntent.ToggleVisibility) }, + ) { + Icon( + imageVector = if (state.hidden) { + Icons.Default.VisibilityOff + } else { + Icons.Default.Visibility + }, + contentDescription = "Toggle visibility", + tint = iconTint, + ) + } + // Like toggle + IconButton( + onClick = { processIntent(GalleryDetailIntent.ToggleLike) }, + ) { + Icon( + imageVector = if (state.liked) { + Icons.Default.Favorite + } else { + Icons.Default.FavoriteBorder + }, + contentDescription = "Toggle like", + tint = if (state.liked) Color.Red else iconTint, + ) + } + // Share button + IconButton( + onClick = { processIntent(GalleryDetailIntent.Export.Image) }, + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(id = PresentationR.drawable.ic_share), + contentDescription = "Share", + colorFilter = ColorFilter.tint(iconTint), + ) + } + // Delete button + IconButton( + onClick = { processIntent(GalleryDetailIntent.Delete.Request) }, + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Delete", + tint = iconTint, + ) + } + } + } + + // Bottom padding for navigation bar height + Spacer(modifier = Modifier.height(16.dp).navigationBarsPadding()) + } + } +} + +@Composable +private fun GalleryDetailContentState( + modifier: Modifier = Modifier, + state: GalleryDetailState.Content, + onCopyTextClick: (CharSequence) -> Unit = {}, + onPageChanged: (Int) -> Unit = {}, + onImageTap: () -> Unit = {}, + onSwipeDown: () -> Unit = {}, + onSwipeUp: () -> Unit = {}, + onDragProgressChanged: (Float) -> Unit = {}, +) { + // Animate background appearance for Immich-style effect + val backgroundAnimatable = remember { Animatable(1f) } + + // Track current drag alpha from ZoomableImage + var currentDragAlpha by remember { mutableFloatStateOf(1f) } + + // Combined background alpha + val backgroundAlpha = backgroundAnimatable.value * currentDragAlpha + + // Notify parent about drag progress for controls fade + LaunchedEffect(backgroundAlpha) { + onDragProgressChanged(backgroundAlpha) + } + + Box( + modifier = modifier + .background(Color.Black.copy(alpha = backgroundAlpha)), + ) { + // Get shared transition scope for Immich-style hero animation + val sharedTransitionScope = LocalSharedTransitionScope.current + val animatedVisibilityScope = LocalAnimatedVisibilityScope.current + + // Remember the initial gallery ID for shared element transition + // This ensures the hero animation targets the correct image + val initialGalleryId = remember { state.id } + + when (state.selectedTab) { + GalleryDetailState.Tab.IMAGE -> { + if (state.galleryIds.size > 1) { + // key() ensures pager is recreated with correct initialPage after deletion + key(state.galleryIds) { + val pagerState = rememberPagerState( + initialPage = state.currentIndex, + pageCount = { state.galleryIds.size } + ) + + // Haptic feedback on page change (like Immich) + val view = LocalView.current + LaunchedEffect(pagerState) { + snapshotFlow { pagerState.settledPage } + .collect { page -> + if (page != state.currentIndex) { + // Haptic feedback like Immich's selectionClick + view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) + onPageChanged(page) + } + } + } + + // Animate to target page (swipe effect for deletion) + LaunchedEffect(state.animateToPage) { + state.animateToPage?.let { targetPage -> + if (targetPage in 0 until pagerState.pageCount && + pagerState.currentPage != targetPage) { + pagerState.animateScrollToPage(targetPage) + } + } + } + + // Fast scroll physics like Immich (FastClampingScrollPhysics) + // No bounce to avoid showing placeholder of adjacent page + val flingBehavior = PagerDefaults.flingBehavior( + state = pagerState, + snapAnimationSpec = spring( + dampingRatio = Spring.DampingRatioNoBouncy, + stiffness = Spring.StiffnessMedium, + ), + ) + + HorizontalPager( + state = pagerState, + modifier = Modifier + .fillMaxSize(), + beyondViewportPageCount = 0, // Don't preload - avoids showing unloaded pages during overscroll + flingBehavior = flingBehavior, + ) { page -> + // Get bitmap from cache, or use current bitmap for current page + val pageBitmap = state.getBitmapForPage(page) + ?: if (page == state.currentIndex) state.bitmap else null + + // Get gallery ID for this page for shared element + val pageGalleryId = state.galleryIds.getOrNull(page) ?: 0L + + // Apply shared element only to the initially opened image + val pageSharedModifier = if ( + sharedTransitionScope != null && + animatedVisibilityScope != null && + pageGalleryId == initialGalleryId + ) { + with(sharedTransitionScope) { + Modifier.sharedElement( + sharedContentState = rememberSharedContentState(key = galleryImageSharedKey(pageGalleryId)), + animatedVisibilityScope = animatedVisibilityScope, + ) + } + } else { + Modifier + } + + if (pageBitmap != null) { + ZoomableImage( + modifier = Modifier + .fillMaxSize() + .then(pageSharedModifier), + source = ZoomableImageSource.Bmp(pageBitmap), + backgroundColor = Color.Transparent, + hideImage = page == state.currentIndex && state.hidden, + consumeGesturesWhenNotZoomed = false, + onTap = onImageTap, + onSwipeUp = onSwipeUp, + onSwipeDown = onSwipeDown, + onDragProgress = { alpha -> + currentDragAlpha = alpha + }, + ) + } else { + // Blur placeholder while loading (like Immich's loadingBuilder) + // Try to get thumbnail from cache for blur effect + val thumbnailBitmap = state.getThumbnailForPage(page) + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black), + contentAlignment = Alignment.Center, + ) { + if (thumbnailBitmap != null) { + // Show blurred thumbnail as placeholder + // RenderEffect requires API 31+ + val blurModifier = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + Modifier.graphicsLayer { + renderEffect = android.graphics.RenderEffect + .createBlurEffect(30f, 30f, android.graphics.Shader.TileMode.CLAMP) + .asComposeRenderEffect() + } + } else { + Modifier + } + Image( + modifier = Modifier + .fillMaxSize() + .then(blurModifier), + bitmap = thumbnailBitmap.asImageBitmap(), + contentDescription = null, + contentScale = androidx.compose.ui.layout.ContentScale.Fit, + ) + } + // Loading indicator on top + CircularProgressIndicator( + modifier = Modifier.size(48.dp), + color = Color.White.copy(alpha = 0.7f), + strokeWidth = 3.dp, + ) + } + } + } + } + } else { + // Single image - apply shared element + val singleImageSharedModifier = if ( + sharedTransitionScope != null && + animatedVisibilityScope != null + ) { + with(sharedTransitionScope) { + Modifier.sharedElement( + sharedContentState = rememberSharedContentState(key = galleryImageSharedKey(state.id)), + animatedVisibilityScope = animatedVisibilityScope, + ) + } + } else { + Modifier + } + ZoomableImage( + modifier = Modifier + .fillMaxSize() + .then(singleImageSharedModifier), + source = ZoomableImageSource.Bmp(state.bitmap), + backgroundColor = Color.Transparent, + hideImage = state.hidden, + onTap = onImageTap, + onSwipeUp = onSwipeUp, + onSwipeDown = onSwipeDown, + onDragProgress = { alpha -> + currentDragAlpha = alpha + }, + ) + } + } + + GalleryDetailState.Tab.ORIGINAL -> state.inputBitmap?.let { bmp -> + ZoomableImage( + modifier = Modifier.fillMaxSize(), + source = ZoomableImageSource.Bmp(bmp), + backgroundColor = Color.Transparent, + onTap = onImageTap, + onSwipeUp = onSwipeUp, + onSwipeDown = onSwipeDown, + onDragProgress = { alpha -> + currentDragAlpha = alpha + }, + ) + } + + GalleryDetailState.Tab.INFO -> GalleryDetailsTable( + modifier = Modifier + .fillMaxSize() + .statusBarsPadding() + .padding(top = 64.dp), + state = state, + onCopyTextClick = onCopyTextClick, + ) + } + } +} + +@Composable +private fun GalleryDetailsTable( + modifier: Modifier = Modifier, + state: GalleryDetailState.Content, + onCopyTextClick: (CharSequence) -> Unit = {}, +) { + Column( + modifier = modifier + .verticalScroll(rememberScrollState()) + .fillMaxSize(), + ) { + val colorOddBg = MaterialTheme.colorScheme.surface + val colorOddText = colors( + light = Catppuccin.Latte.Text, + dark = Catppuccin.Frappe.Text + ) + val colorEvenBg = MaterialTheme.colorScheme.surfaceTint + GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = LocalizationR.string.gallery_info_field_date.asUiText(), + value = state.createdAt, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + GalleryDetailRow( + modifier = Modifier.background(color = colorEvenBg), + name = LocalizationR.string.gallery_info_field_type.asUiText(), + value = state.type, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + if (state.modelName.asString().isNotBlank()) { + GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = LocalizationR.string.gallery_info_field_model.asUiText(), + value = state.modelName, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + } + if (state.isFalAi) { + // Fal AI specific fields + GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = "Endpoint".asUiText(), + value = state.sampler, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + } + GalleryDetailRow( + modifier = Modifier.background(color = if (state.isFalAi) colorEvenBg else colorOddBg), + name = LocalizationR.string.gallery_info_field_prompt.asUiText(), + value = state.prompt, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + if (!state.isFalAi || state.negativePrompt.asString().isNotBlank()) { + GalleryDetailRow( + modifier = Modifier.background(color = if (state.isFalAi) colorOddBg else colorEvenBg), + name = LocalizationR.string.gallery_info_field_negative_prompt.asUiText(), + value = state.negativePrompt, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + } + GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = LocalizationR.string.gallery_info_field_size.asUiText(), + value = state.size, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + if (!state.isFalAi) { + // SD specific fields - hide for Fal AI + GalleryDetailRow( + modifier = Modifier.background(color = colorEvenBg), + name = LocalizationR.string.gallery_info_field_sampling_steps.asUiText(), + value = state.samplingSteps, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = LocalizationR.string.gallery_info_field_cfg.asUiText(), + value = state.cfgScale, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + GalleryDetailRow( + modifier = Modifier.background(color = colorEvenBg), + name = LocalizationR.string.gallery_info_field_restore_faces.asUiText(), + value = state.restoreFaces, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = LocalizationR.string.gallery_info_field_sampler.asUiText(), + value = state.sampler, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + } + if (state.seed.asString().isNotBlank()) { + GalleryDetailRow( + modifier = Modifier.background(color = colorEvenBg), + name = LocalizationR.string.gallery_info_field_seed.asUiText(), + value = state.seed, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + } + if (!state.isFalAi) { + GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = LocalizationR.string.gallery_info_field_sub_seed.asUiText(), + value = state.subSeed, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + GalleryDetailRow( + modifier = Modifier.background(color = colorEvenBg), + name = LocalizationR.string.gallery_info_field_sub_seed_strength.asUiText(), + value = state.subSeedStrength, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + if (state.generationType == AiGenerationResult.Type.IMAGE_TO_IMAGE) GalleryDetailRow( + modifier = Modifier.background(color = colorOddBg), + name = LocalizationR.string.gallery_info_field_denoising_strength.asUiText(), + value = state.denoisingStrength, + color = colorOddText, + onCopyTextClick = onCopyTextClick, + ) + } + } +} + +@Composable +private fun GalleryDetailRow( + modifier: Modifier = Modifier, + column1Weight: Float = 0.4f, + column2Weight: Float = 0.6f, + name: UiText, + value: UiText, + color: Color, + onCopyTextClick: (CharSequence) -> Unit = {}, +) { + val rawValue = value.asString() + Row(modifier) { + GalleryDetailCell( + text = name, + modifier = Modifier.weight(column1Weight), + color = color, + ) + GalleryDetailCell( + text = value, + modifier = Modifier.weight(column2Weight), + color = color, + ) + if (rawValue.isNotBlank()) { + IconButton( + onClick = { onCopyTextClick(rawValue) }, + ) { + Icon( + modifier = Modifier.size(20.dp), + imageVector = Icons.Default.ContentCopy, + contentDescription = "Copy", + tint = MaterialTheme.colorScheme.primary, + ) + } + } + } +} + +@Composable +private fun GalleryDetailCell( + modifier: Modifier = Modifier, + text: UiText, + color: Color, +) { + Text( + modifier = modifier + .padding(start = 12.dp) + .padding(vertical = 8.dp), + text = text.asString(), + color = color, + ) +} + +@Composable +@Preview(showSystemUi = true, showBackground = true) +private fun PreviewGalleryScreenTxt2ImgContentTabImage() { + ScreenContent(state = mockGalleryDetailTxt2Img) +} + +@Composable +@Preview(showSystemUi = true, showBackground = true) +private fun PreviewGalleryScreenTxt2ImgContentTabInfo() { + ScreenContent(state = mockGalleryDetailTxt2Img.copy(selectedTab = GalleryDetailState.Tab.INFO)) +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailSharing.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailSharing.kt similarity index 93% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailSharing.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailSharing.kt index 4cf2cb158..26212df32 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailSharing.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailSharing.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.screen.gallery.detail +package dev.minios.pdaiv1.presentation.screen.gallery.detail import android.content.Context -import com.shifthackz.aisdv1.core.sharing.shareText -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.sharing.shareText +import dev.minios.pdaiv1.core.localization.R as LocalizationR class GalleryDetailSharing { diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailState.kt new file mode 100644 index 000000000..805d74f75 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailState.kt @@ -0,0 +1,195 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.detail + +import android.graphics.Bitmap +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.extensions.mapToUi +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviState +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.R as PresentationR + +sealed interface GalleryDetailState : MviState { + val tabs: List + val selectedTab: Tab + val screenModal: Modal + val galleryIds: List + val currentIndex: Int + val bitmapCache: Map + val controlsVisible: Boolean + val currentSource: ServerSource + val animateToPage: Int? + val showInfoBottomSheet: Boolean + + @Immutable + data class Loading( + override val tabs: List = emptyList(), + override val selectedTab: Tab = Tab.IMAGE, + override val screenModal: Modal = Modal.None, + override val galleryIds: List = emptyList(), + override val currentIndex: Int = 0, + override val bitmapCache: Map = emptyMap(), + override val controlsVisible: Boolean = true, + override val currentSource: ServerSource = ServerSource.AUTOMATIC1111, + override val animateToPage: Int? = null, + override val showInfoBottomSheet: Boolean = false, + ) : GalleryDetailState + + @Immutable + data class Content( + override val tabs: List = emptyList(), + override val selectedTab: Tab = Tab.IMAGE, + override val screenModal: Modal = Modal.None, + override val galleryIds: List = emptyList(), + override val currentIndex: Int = 0, + override val bitmapCache: Map = emptyMap(), + override val controlsVisible: Boolean = true, + override val currentSource: ServerSource = ServerSource.AUTOMATIC1111, + override val animateToPage: Int? = null, + override val showInfoBottomSheet: Boolean = false, + val showReportButton: Boolean = false, + val generationType: AiGenerationResult.Type, + val id: Long, + val bitmap: Bitmap, + val inputBitmap: Bitmap?, + val createdAt: UiText, + val type: UiText, + val prompt: UiText, + val negativePrompt: UiText, + val size: UiText, + val samplingSteps: UiText, + val cfgScale: UiText, + val restoreFaces: UiText, + val sampler: UiText, + val seed: UiText, + val subSeed: UiText, + val subSeedStrength: UiText, + val denoisingStrength: UiText, + val hidden: Boolean, + val liked: Boolean = false, + val isFalAi: Boolean = false, + val falAiEndpointId: String = "", + val modelName: UiText = "".asUiText(), + ) : GalleryDetailState + + fun withTab(tab: Tab): GalleryDetailState = when (this) { + is Content -> copy(selectedTab = tab) + is Loading -> copy(selectedTab = tab) + } + + fun withDialog(dialog: Modal) = when (this) { + is Content -> copy(screenModal = dialog) + is Loading -> copy(screenModal = dialog) + } + + fun withHiddenState(value: Boolean) = when (this) { + is Content -> copy(hidden = value) + is Loading -> this + } + + fun withLikedState(value: Boolean) = when (this) { + is Content -> copy(liked = value) + is Loading -> this + } + + fun withControlsVisible(value: Boolean) = when (this) { + is Content -> copy(controlsVisible = value) + is Loading -> copy(controlsVisible = value) + } + + fun withInfoBottomSheet(value: Boolean) = when (this) { + is Content -> copy(showInfoBottomSheet = value) + is Loading -> copy(showInfoBottomSheet = value) + } + + fun withGalleryIds(ids: List, index: Int) = when (this) { + is Content -> copy(galleryIds = ids, currentIndex = index) + is Loading -> copy(galleryIds = ids, currentIndex = index) + } + + fun withBitmapCache(id: Long, bitmap: Bitmap) = when (this) { + is Content -> copy(bitmapCache = bitmapCache + (id to bitmap)) + is Loading -> copy(bitmapCache = bitmapCache + (id to bitmap)) + } + + fun getBitmapForPage(pageIndex: Int): Bitmap? { + if (pageIndex !in galleryIds.indices) return null + val id = galleryIds[pageIndex] + return bitmapCache[id] + } + + /** + * Get thumbnail for blur placeholder (like Immich's loadingBuilder). + * Returns any available bitmap for blur effect while loading. + */ + fun getThumbnailForPage(pageIndex: Int): Bitmap? { + // First try to get cached bitmap for this page + val cached = getBitmapForPage(pageIndex) + if (cached != null) return cached + + // Fallback: use current bitmap if available (for adjacent pages) + return when (this) { + is Content -> bitmap + is Loading -> null + } + } + + enum class Tab( + @StringRes val label: Int, + @DrawableRes val iconRes: Int, + ) { + IMAGE(LocalizationR.string.gallery_tab_image, PresentationR.drawable.ic_image), + ORIGINAL(LocalizationR.string.gallery_tab_original, PresentationR.drawable.ic_image), + INFO(LocalizationR.string.gallery_tab_info, PresentationR.drawable.ic_text); + + companion object { + fun consume(type: AiGenerationResult.Type): List = when (type) { + AiGenerationResult.Type.TEXT_TO_IMAGE -> listOf( + IMAGE, INFO, + ) + + AiGenerationResult.Type.IMAGE_TO_IMAGE -> entries + } + } + } +} + +fun Triple.mapToUi( + currentSource: ServerSource = ServerSource.AUTOMATIC1111, +): GalleryDetailState.Content = + let { (ai, out, original) -> + val isFalAi = ai.sampler.startsWith("fal.ai/") + val falAiEndpointId = if (isFalAi) ai.sampler.removePrefix("fal.ai/") else "" + GalleryDetailState.Content( + tabs = GalleryDetailState.Tab.consume(ai.type), + currentSource = currentSource, + generationType = ai.type, + id = ai.id, + bitmap = out.bitmap, + inputBitmap = original?.bitmap, + createdAt = ai.createdAt.toString().asUiText(), + type = if (isFalAi) "Fal AI".asUiText() else ai.type.key.asUiText(), + prompt = ai.prompt.asUiText(), + negativePrompt = ai.negativePrompt.asUiText(), + size = "${ai.width} X ${ai.height}".asUiText(), + samplingSteps = ai.samplingSteps.toString().asUiText(), + cfgScale = ai.cfgScale.toString().asUiText(), + restoreFaces = ai.restoreFaces.mapToUi(), + sampler = if (isFalAi) falAiEndpointId.asUiText() else ai.sampler.asUiText(), + seed = ai.seed.asUiText(), + subSeed = ai.subSeed.asUiText(), + subSeedStrength = ai.subSeedStrength.toString().asUiText(), + denoisingStrength = ai.denoisingStrength.toString().asUiText(), + hidden = ai.hidden, + liked = ai.liked, + isFalAi = isFalAi, + falAiEndpointId = falAiEndpointId, + modelName = ai.modelName.asUiText(), + ) + } diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailViewModel.kt new file mode 100644 index 000000000..24ad8cc22 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailViewModel.kt @@ -0,0 +1,358 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.detail + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter.Input +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.caching.GetLastResultFromCacheUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryPagedIdsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleImageVisibilityUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleLikeUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultUseCase +import dev.minios.pdaiv1.presentation.core.GalleryItemStateEvent +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.kotlin.subscribeBy +import java.io.ByteArrayOutputStream +import java.util.concurrent.TimeUnit + +class GalleryDetailViewModel( + private val itemId: Long, + private val onNavigateBackCallback: (() -> Unit)? = null, + dispatchersProvider: DispatchersProvider, + private val buildInfoProvider: BuildInfoProvider, + private val preferenceManager: PreferenceManager, + private val getGenerationResultUseCase: GetGenerationResultUseCase, + private val getLastResultFromCacheUseCase: GetLastResultFromCacheUseCase, + private val getGalleryPagedIdsUseCase: GetGalleryPagedIdsUseCase, + private val deleteGalleryItemUseCase: DeleteGalleryItemUseCase, + private val toggleImageVisibilityUseCase: ToggleImageVisibilityUseCase, + private val toggleLikeUseCase: ToggleLikeUseCase, + private val galleryDetailBitmapExporter: GalleryDetailBitmapExporter, + private val base64ToBitmapConverter: Base64ToBitmapConverter, + private val schedulersProvider: SchedulersProvider, + private val generationFormUpdateEvent: GenerationFormUpdateEvent, + private val galleryItemStateEvent: GalleryItemStateEvent, + private val mainRouter: MainRouter, + private val mediaStoreGateway: MediaStoreGateway, + private val backgroundWorkObserver: BackgroundWorkObserver, +) : MviRxViewModel() { + + override val initialState = GalleryDetailState.Loading(currentSource = preferenceManager.source) + + override val effectDispatcher = dispatchersProvider.immediate + + private var currentItemId: Long = itemId + + // Helper to navigate back - uses callback if available, otherwise mainRouter + private fun navigateBack() { + onNavigateBackCallback?.invoke() ?: mainRouter.navigateBack() + } + + init { + // Load all gallery IDs first, then load the current item + !getGalleryPagedIdsUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { ids -> + val index = ids.indexOf(itemId).coerceAtLeast(0) + updateState { it.withGalleryIds(ids, index) } + loadItem(itemId) + } + } + + private fun loadItem(id: Long) { + currentItemId = id + !getGenerationResult(id) + .subscribeOnMainThread(schedulersProvider) + .postProcess() + .subscribeBy(::errorLog) { ai -> + updateState { state -> + val newIndex = state.galleryIds.indexOf(id).coerceAtLeast(0) + ai.mapToUi(preferenceManager.source) + .copy( + showReportButton = buildInfoProvider.type == BuildType.PLAY, + galleryIds = state.galleryIds, + currentIndex = newIndex, + bitmapCache = state.bitmapCache + (id to ai.second.bitmap), + controlsVisible = state.controlsVisible, + ) + .withTab(state.selectedTab) + } + // Preload adjacent images + preloadAdjacentImages(id) + } + } + + private fun preloadAdjacentImages(currentId: Long) { + val ids = currentState.galleryIds + val currentIndex = ids.indexOf(currentId) + if (currentIndex < 0) return + + // Preload previous and next images + listOf(currentIndex - 1, currentIndex + 1) + .filter { it in ids.indices } + .map { ids[it] } + .filter { it !in currentState.bitmapCache } + .forEach { adjacentId -> + preloadBitmap(adjacentId) + } + } + + private fun preloadBitmap(id: Long) { + !getGenerationResult(id) + .subscribeOn(schedulersProvider.io) + .flatMap { ai -> + base64ToBitmapConverter(Input(ai.image)).map { bmp -> id to bmp.bitmap } + } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { /* Ignore preload errors */ }, + onSuccess = { (loadedId, bitmap) -> + updateState { state -> + state.withBitmapCache(loadedId, bitmap) + } + } + ) + } + + override fun processIntent(intent: GalleryDetailIntent) { + when (intent) { + is GalleryDetailIntent.CopyToClipboard -> { + emitEffect(GalleryDetailEffect.ShareClipBoard(intent.content.toString())) + } + + GalleryDetailIntent.Delete.Request -> setActiveModal( + Modal.DeleteImageConfirm(false, isMultiple = false) + ) + + GalleryDetailIntent.Delete.Confirm -> { + setActiveModal(Modal.None) + delete() + } + + GalleryDetailIntent.Export.Image -> share() + + GalleryDetailIntent.Export.Params -> { + emitEffect(GalleryDetailEffect.ShareGenerationParams(currentState)) + } + + GalleryDetailIntent.NavigateBack -> navigateBack() + + is GalleryDetailIntent.SelectTab -> updateState { + it.withTab(intent.tab) + } + + GalleryDetailIntent.SendTo.Txt2Img -> sendPromptToGenerationScreen( + AiGenerationResult.Type.TEXT_TO_IMAGE, + ) + + GalleryDetailIntent.SendTo.Img2Img -> sendPromptToGenerationScreen( + AiGenerationResult.Type.IMAGE_TO_IMAGE, + ) + + GalleryDetailIntent.SendTo.FalAi -> sendPromptToFalAi() + + GalleryDetailIntent.DismissDialog -> setActiveModal(Modal.None) + + GalleryDetailIntent.Report -> (currentState as? GalleryDetailState.Content) + ?.id + ?.let(mainRouter::navigateToReportImage) + + GalleryDetailIntent.ToggleVisibility -> toggleVisibility() + + GalleryDetailIntent.ToggleLike -> toggleLike() + + GalleryDetailIntent.ToggleControlsVisibility -> updateState { + it.withControlsVisible(!it.controlsVisible) + } + + GalleryDetailIntent.ShowInfoBottomSheet -> updateState { + it.withInfoBottomSheet(true) + } + + GalleryDetailIntent.HideInfoBottomSheet -> updateState { + it.withInfoBottomSheet(false) + } + + GalleryDetailIntent.OpenEditor -> { + (currentState as? GalleryDetailState.Content)?.id?.let { id -> + mainRouter.navigateToImageEditor(id) + } + } + + GalleryDetailIntent.SaveToGallery -> saveToGallery() + + is GalleryDetailIntent.PageChanged -> { + val ids = currentState.galleryIds + if (intent.index in ids.indices) { + val newId = ids[intent.index] + if (newId != currentItemId) { + loadItem(newId) + } + } + } + } + } + + private fun saveToGallery() { + val state = currentState as? GalleryDetailState.Content ?: return + val bitmap = state.bitmap + !Completable.fromAction { + val stream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) + mediaStoreGateway.exportToFile( + fileName = "pdai_${System.currentTimeMillis()}", + content = stream.toByteArray(), + ) + } + .subscribeOn(schedulersProvider.io) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { + emitEffect(GalleryDetailEffect.ImageSavedToGallery) + } + } + + private fun share() { + val state = currentState as? GalleryDetailState.Content ?: return + val bitmap = if ( + state.generationType == AiGenerationResult.Type.IMAGE_TO_IMAGE + && state.inputBitmap != null + && state.selectedTab == GalleryDetailState.Tab.ORIGINAL + ) { + state.inputBitmap + } else { + state.bitmap + } + !galleryDetailBitmapExporter(bitmap) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { file -> + emitEffect(GalleryDetailEffect.ShareImageFile(file)) + } + } + + private fun delete() { + val state = currentState as? GalleryDetailState.Content ?: return + val deletedId = state.id + val ids = state.galleryIds + val currentIndex = ids.indexOf(deletedId) + + val newIds = ids.filter { it != deletedId } + + if (newIds.isEmpty()) { + // No more images after deletion, just delete and go back + !deleteGalleryItemUseCase(deletedId) + .doOnComplete { backgroundWorkObserver.postGalleryChangedSignal() } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { + navigateBack() + } + } else { + // Determine target page for swipe animation (next or previous) + val targetPage = if (currentIndex < ids.size - 1) { + currentIndex + 1 + } else { + currentIndex - 1 + } + + // Trigger swipe animation to next/previous page + updateState { + (it as? GalleryDetailState.Content)?.copy(animateToPage = targetPage) ?: it + } + + // After animation completes, delete and update state + !deleteGalleryItemUseCase(deletedId) + .doOnComplete { backgroundWorkObserver.postGalleryChangedSignal() } + .delay(350, TimeUnit.MILLISECONDS) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { + val nextIndex = currentIndex.coerceAtMost(newIds.size - 1) + val nextId = newIds[nextIndex] + + updateState { + (it as? GalleryDetailState.Content)?.copy( + galleryIds = newIds, + currentIndex = nextIndex, + animateToPage = null + ) ?: it.withGalleryIds(newIds, nextIndex) + } + loadItem(nextId) + } + } + } + + private fun setActiveModal(dialog: Modal) = updateState { + it.withDialog(dialog) + } + + private fun Single.postProcess() = this + .flatMap { ai -> + base64ToBitmapConverter(Input(ai.image)).map { bmp -> ai to bmp } + } + .flatMap { (ai, bmp) -> + when (ai.type) { + AiGenerationResult.Type.TEXT_TO_IMAGE -> Single.just(Triple(ai, bmp, null)) + AiGenerationResult.Type.IMAGE_TO_IMAGE -> + base64ToBitmapConverter(Input(ai.inputImage)).map { bmp2 -> + Triple(ai, bmp, bmp2) + } + } + } + + private fun sendPromptToGenerationScreen(screenType: AiGenerationResult.Type) { + val state = (currentState as? GalleryDetailState.Content) ?: return + !getGenerationResult(currentItemId) + .subscribeOnMainThread(schedulersProvider) + .doFinally { navigateBack() } + .subscribeBy(::errorLog) { ai -> + generationFormUpdateEvent.update( + ai, + screenType, + state.selectedTab == GalleryDetailState.Tab.ORIGINAL, + ) + } + + } + + private fun sendPromptToFalAi() { + !getGenerationResult(currentItemId) + .subscribeOnMainThread(schedulersProvider) + .doFinally { navigateBack() } + .subscribeBy(::errorLog) { ai -> + generationFormUpdateEvent.updateFalAi(ai) + } + } + + private fun toggleVisibility() = !toggleImageVisibilityUseCase(currentItemId) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { hidden -> + updateState { it.withHiddenState(hidden) } + galleryItemStateEvent.emitHiddenChange(currentItemId, hidden) + } + + private fun toggleLike() = !toggleLikeUseCase(currentItemId) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { liked -> + updateState { it.withLikedState(liked) } + galleryItemStateEvent.emitLikedChange(currentItemId, liked) + } + + private fun getGenerationResult(id: Long): Single { + if (id <= 0) return getLastResultFromCacheUseCase() + return getGenerationResultUseCase(id) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorEffect.kt new file mode 100644 index 000000000..498e5783b --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorEffect.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.editor + +import com.shifthackz.android.core.mvi.MviEffect + +sealed interface ImageEditorEffect : MviEffect { + + data object SavedSuccessfully : ImageEditorEffect + + data object SavedAsNewImage : ImageEditorEffect +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorIntent.kt new file mode 100644 index 000000000..46103204b --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorIntent.kt @@ -0,0 +1,36 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.editor + +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface ImageEditorIntent : MviIntent { + + data object NavigateBack : ImageEditorIntent + + data object RotateLeft : ImageEditorIntent + + data object RotateRight : ImageEditorIntent + + data object FlipHorizontal : ImageEditorIntent + + data object FlipVertical : ImageEditorIntent + + data class UpdateBrightness(val value: Float) : ImageEditorIntent + + data class UpdateContrast(val value: Float) : ImageEditorIntent + + data class UpdateSaturation(val value: Float) : ImageEditorIntent + + data object ResetFilters : ImageEditorIntent + + data object Save : ImageEditorIntent + + data object SaveAs : ImageEditorIntent + + data object DismissDialog : ImageEditorIntent + + enum class Tool { + ROTATE, ADJUST; + } + + data class SelectTool(val tool: Tool) : ImageEditorIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorScreen.kt new file mode 100644 index 000000000..094e1ff5b --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorScreen.kt @@ -0,0 +1,513 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package dev.minios.pdaiv1.presentation.screen.gallery.editor + +import android.widget.Toast +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.RotateLeft +import androidx.compose.material.icons.automirrored.outlined.RotateRight +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.FlipCameraAndroid +import androidx.compose.material.icons.filled.Tune +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.shifthackz.android.core.mvi.MviComponent +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun ImageEditorScreen(itemId: Long) { + val context = LocalContext.current + + MviComponent( + viewModel = koinViewModel( + parameters = { parametersOf(itemId) }, + ), + processEffect = { effect -> + when (effect) { + ImageEditorEffect.SavedSuccessfully -> { + Toast.makeText( + context, + context.getString(LocalizationR.string.gallery_save_success), + Toast.LENGTH_SHORT, + ).show() + } + ImageEditorEffect.SavedAsNewImage -> { + Toast.makeText( + context, + context.getString(LocalizationR.string.gallery_save_success), + Toast.LENGTH_SHORT, + ).show() + } + } + }, + ) { state, intentHandler -> + ScreenContent( + modifier = Modifier.fillMaxSize(), + state = state, + processIntent = intentHandler, + ) + } +} + +@Composable +private fun ScreenContent( + modifier: Modifier = Modifier, + state: ImageEditorState, + processIntent: (ImageEditorIntent) -> Unit = {}, +) { + var showRotateSheet by remember { mutableStateOf(false) } + var showAdjustSheet by remember { mutableStateOf(false) } + + Box( + modifier = modifier.background(Color.Black), + ) { + // Image content + Box( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 140.dp), + contentAlignment = Alignment.Center, + ) { + if (state.isLoading) { + CircularProgressIndicator( + color = Color.White, + ) + } else { + state.displayBitmap?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Edited image", + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentScale = ContentScale.Fit, + ) + } + } + } + + // Top bar + Row( + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .padding(horizontal = 8.dp, vertical = 8.dp) + .align(Alignment.TopCenter), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + onClick = { processIntent(ImageEditorIntent.NavigateBack) }, + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close", + tint = Color.White, + modifier = Modifier.size(28.dp), + ) + } + + Text( + text = stringResource(LocalizationR.string.edit), + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + ) + + TextButton( + onClick = { processIntent(ImageEditorIntent.Save) }, + enabled = state.hasChanges && !state.isSaving, + ) { + if (state.isSaving) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + color = Color.White, + strokeWidth = 2.dp, + ) + } else { + Text( + text = stringResource(LocalizationR.string.gallery_save_to_gallery), + color = if (state.hasChanges) MaterialTheme.colorScheme.primary else Color.Gray, + fontWeight = FontWeight.Medium, + ) + } + } + } + + // Bottom navigation bar (Immich style) + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp, vertical = 24.dp) + .navigationBarsPadding(), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(70.dp) + .clip(RoundedCornerShape(35.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.95f)), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + EditorToolButton( + icon = Icons.AutoMirrored.Outlined.RotateRight, + label = stringResource(LocalizationR.string.editor_rotate), + onClick = { showRotateSheet = true }, + ) + EditorToolButton( + icon = Icons.Default.Tune, + label = stringResource(LocalizationR.string.editor_adjust), + onClick = { showAdjustSheet = true }, + ) + } + } + + // Reset button (shown when there are changes) + AnimatedVisibility( + visible = state.hasChanges, + enter = fadeIn() + slideInVertically { it }, + exit = fadeOut() + slideOutVertically { it }, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 110.dp) + .navigationBarsPadding(), + ) { + TextButton( + onClick = { processIntent(ImageEditorIntent.ResetFilters) }, + ) { + Text( + text = "Reset", + color = Color.White.copy(alpha = 0.7f), + ) + } + } + + // Rotate/Flip Bottom Sheet + if (showRotateSheet) { + ModalBottomSheet( + onDismissRequest = { showRotateSheet = false }, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + containerColor = MaterialTheme.colorScheme.surface, + dragHandle = { + Box( + modifier = Modifier + .padding(vertical = 12.dp) + .size(width = 40.dp, height = 4.dp) + .background( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), + shape = RoundedCornerShape(2.dp) + ) + ) + }, + ) { + RotateFlipContent( + onRotateLeft = { processIntent(ImageEditorIntent.RotateLeft) }, + onRotateRight = { processIntent(ImageEditorIntent.RotateRight) }, + onFlipH = { processIntent(ImageEditorIntent.FlipHorizontal) }, + onFlipV = { processIntent(ImageEditorIntent.FlipVertical) }, + onDismiss = { showRotateSheet = false }, + ) + } + } + + // Adjust Bottom Sheet + if (showAdjustSheet) { + ModalBottomSheet( + onDismissRequest = { showAdjustSheet = false }, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + containerColor = MaterialTheme.colorScheme.surface, + dragHandle = { + Box( + modifier = Modifier + .padding(vertical = 12.dp) + .size(width = 40.dp, height = 4.dp) + .background( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), + shape = RoundedCornerShape(2.dp) + ) + ) + }, + ) { + AdjustContent( + brightness = state.brightness, + contrast = state.contrast, + saturation = state.saturation, + onBrightnessChange = { processIntent(ImageEditorIntent.UpdateBrightness(it)) }, + onContrastChange = { processIntent(ImageEditorIntent.UpdateContrast(it)) }, + onSaturationChange = { processIntent(ImageEditorIntent.UpdateSaturation(it)) }, + onDismiss = { showAdjustSheet = false }, + ) + } + } + } +} + +@Composable +private fun EditorToolButton( + icon: ImageVector, + label: String, + onClick: () -> Unit, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 24.dp), + ) { + IconButton(onClick = onClick) { + Icon( + imageVector = icon, + contentDescription = label, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(28.dp), + ) + } + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } +} + +@Composable +private fun RotateFlipContent( + onRotateLeft: () -> Unit, + onRotateRight: () -> Unit, + onFlipH: () -> Unit, + onFlipV: () -> Unit, + onDismiss: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp) + .navigationBarsPadding(), + ) { + Text( + text = stringResource(LocalizationR.string.editor_rotate), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(bottom = 24.dp), + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + RotateFlipButton( + icon = Icons.AutoMirrored.Outlined.RotateLeft, + label = "90° Left", + onClick = onRotateLeft, + ) + RotateFlipButton( + icon = Icons.AutoMirrored.Outlined.RotateRight, + label = "90° Right", + onClick = onRotateRight, + ) + RotateFlipButton( + icon = Icons.Default.FlipCameraAndroid, + label = "Flip H", + onClick = onFlipH, + ) + RotateFlipButton( + icon = Icons.Default.FlipCameraAndroid, + label = "Flip V", + onClick = onFlipV, + iconRotation = 90f, + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + TextButton( + onClick = onDismiss, + modifier = Modifier.align(Alignment.End), + ) { + Text("Done") + } + } +} + +@Composable +private fun RotateFlipButton( + icon: ImageVector, + label: String, + onClick: () -> Unit, + iconRotation: Float = 0f, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + IconButton( + onClick = onClick, + modifier = Modifier + .size(56.dp) + .clip(RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant), + ) { + Icon( + imageVector = icon, + contentDescription = label, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(28.dp), + ) + } + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } +} + +@Composable +private fun AdjustContent( + brightness: Float, + contrast: Float, + saturation: Float, + onBrightnessChange: (Float) -> Unit, + onContrastChange: (Float) -> Unit, + onSaturationChange: (Float) -> Unit, + onDismiss: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp) + .navigationBarsPadding(), + ) { + Text( + text = stringResource(LocalizationR.string.editor_adjust), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(bottom = 24.dp), + ) + + // Brightness + AdjustSlider( + label = stringResource(LocalizationR.string.editor_brightness), + value = brightness, + valueRange = -1f..1f, + onValueChange = onBrightnessChange, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Contrast + AdjustSlider( + label = stringResource(LocalizationR.string.editor_contrast), + value = contrast, + valueRange = 0.5f..2f, + defaultValue = 1f, + onValueChange = onContrastChange, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Saturation + AdjustSlider( + label = stringResource(LocalizationR.string.editor_saturation), + value = saturation, + valueRange = 0f..2f, + defaultValue = 1f, + onValueChange = onSaturationChange, + ) + + Spacer(modifier = Modifier.height(24.dp)) + + TextButton( + onClick = onDismiss, + modifier = Modifier.align(Alignment.End), + ) { + Text("Done") + } + } +} + +@Composable +private fun AdjustSlider( + label: String, + value: Float, + valueRange: ClosedFloatingPointRange, + defaultValue: Float = 0f, + onValueChange: (Float) -> Unit, +) { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = String.format("%.0f%%", ((value - defaultValue) / (valueRange.endInclusive - valueRange.start)) * 200), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Spacer(modifier = Modifier.height(8.dp)) + Slider( + value = value, + onValueChange = onValueChange, + valueRange = valueRange, + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant, + ), + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorState.kt new file mode 100644 index 000000000..4709ae5c4 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorState.kt @@ -0,0 +1,36 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.editor + +import android.graphics.Bitmap +import androidx.compose.runtime.Immutable +import com.shifthackz.android.core.mvi.MviState +import dev.minios.pdaiv1.presentation.model.Modal + +@Immutable +data class ImageEditorState( + val originalBitmap: Bitmap? = null, + val editedBitmap: Bitmap? = null, + val rotation: Float = 0f, + val flipHorizontal: Boolean = false, + val flipVertical: Boolean = false, + val brightness: Float = 0f, // -1 to 1 + val contrast: Float = 1f, // 0 to 2 + val saturation: Float = 1f, // 0 to 2 + val selectedTool: ImageEditorIntent.Tool = ImageEditorIntent.Tool.ADJUST, + val isLoading: Boolean = true, + val isSaving: Boolean = false, + val screenModal: Modal = Modal.None, + val hasChanges: Boolean = false, +) : MviState { + + val displayBitmap: Bitmap? + get() = editedBitmap ?: originalBitmap + + fun withFiltersApplied(): ImageEditorState = copy( + hasChanges = rotation != 0f || + flipHorizontal || + flipVertical || + brightness != 0f || + contrast != 1f || + saturation != 1f + ) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorViewModel.kt new file mode 100644 index 000000000..676508872 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/editor/ImageEditorViewModel.kt @@ -0,0 +1,256 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.editor + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.Matrix +import android.graphics.Paint +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultUseCase +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.kotlin.subscribeBy +import java.io.ByteArrayOutputStream + +class ImageEditorViewModel( + private val itemId: Long, + dispatchersProvider: DispatchersProvider, + private val getGenerationResultUseCase: GetGenerationResultUseCase, + private val base64ToBitmapConverter: Base64ToBitmapConverter, + private val mediaStoreGateway: MediaStoreGateway, + private val schedulersProvider: SchedulersProvider, + private val mainRouter: MainRouter, +) : MviRxViewModel() { + + override val initialState = ImageEditorState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + loadImage() + } + + private fun loadImage() { + !getGenerationResultUseCase(itemId) + .flatMap { item -> + base64ToBitmapConverter(Base64ToBitmapConverter.Input(item.image)) + .map { it.bitmap } + } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { bitmap -> + updateState { + it.copy( + originalBitmap = bitmap, + editedBitmap = bitmap, + isLoading = false + ) + } + } + } + + override fun processIntent(intent: ImageEditorIntent) { + when (intent) { + ImageEditorIntent.NavigateBack -> mainRouter.navigateBack() + + ImageEditorIntent.RotateLeft -> { + updateState { + it.copy(rotation = (it.rotation - 90f) % 360f).withFiltersApplied() + } + applyTransformations() + } + + ImageEditorIntent.RotateRight -> { + updateState { + it.copy(rotation = (it.rotation + 90f) % 360f).withFiltersApplied() + } + applyTransformations() + } + + ImageEditorIntent.FlipHorizontal -> { + updateState { + it.copy(flipHorizontal = !it.flipHorizontal).withFiltersApplied() + } + applyTransformations() + } + + ImageEditorIntent.FlipVertical -> { + updateState { + it.copy(flipVertical = !it.flipVertical).withFiltersApplied() + } + applyTransformations() + } + + is ImageEditorIntent.UpdateBrightness -> { + updateState { + it.copy(brightness = intent.value).withFiltersApplied() + } + applyTransformations() + } + + is ImageEditorIntent.UpdateContrast -> { + updateState { + it.copy(contrast = intent.value).withFiltersApplied() + } + applyTransformations() + } + + is ImageEditorIntent.UpdateSaturation -> { + updateState { + it.copy(saturation = intent.value).withFiltersApplied() + } + applyTransformations() + } + + ImageEditorIntent.ResetFilters -> updateState { + it.copy( + rotation = 0f, + flipHorizontal = false, + flipVertical = false, + brightness = 0f, + contrast = 1f, + saturation = 1f, + editedBitmap = it.originalBitmap, + hasChanges = false + ) + } + + ImageEditorIntent.Save -> saveImage() + + ImageEditorIntent.SaveAs -> saveImage() + + ImageEditorIntent.DismissDialog -> updateState { + it.copy(screenModal = Modal.None) + } + + is ImageEditorIntent.SelectTool -> updateState { + it.copy(selectedTool = intent.tool) + } + } + } + + private fun applyTransformations() { + val state = currentState + val original = state.originalBitmap ?: return + + !Single.fromCallable { + applyAllEffects( + bitmap = original, + rotation = state.rotation, + flipH = state.flipHorizontal, + flipV = state.flipVertical, + brightness = state.brightness, + contrast = state.contrast, + saturation = state.saturation + ) + } + .subscribeOn(schedulersProvider.io) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { result -> + updateState { it.copy(editedBitmap = result) } + } + } + + private fun applyAllEffects( + bitmap: Bitmap, + rotation: Float, + flipH: Boolean, + flipV: Boolean, + brightness: Float, + contrast: Float, + saturation: Float + ): Bitmap { + // Apply rotation and flip + val matrix = Matrix().apply { + postRotate(rotation) + if (flipH) postScale(-1f, 1f, bitmap.width / 2f, bitmap.height / 2f) + if (flipV) postScale(1f, -1f, bitmap.width / 2f, bitmap.height / 2f) + } + + val rotatedBitmap = Bitmap.createBitmap( + bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true + ) + + // Apply color adjustments + val colorMatrix = ColorMatrix() + + // Brightness: add to RGB values + val brightnessMatrix = ColorMatrix(floatArrayOf( + 1f, 0f, 0f, 0f, brightness * 255, + 0f, 1f, 0f, 0f, brightness * 255, + 0f, 0f, 1f, 0f, brightness * 255, + 0f, 0f, 0f, 1f, 0f + )) + + // Contrast: scale RGB values around middle + val contrastMatrix = ColorMatrix(floatArrayOf( + contrast, 0f, 0f, 0f, 128 * (1 - contrast), + 0f, contrast, 0f, 0f, 128 * (1 - contrast), + 0f, 0f, contrast, 0f, 128 * (1 - contrast), + 0f, 0f, 0f, 1f, 0f + )) + + // Saturation + val saturationMatrix = ColorMatrix() + saturationMatrix.setSaturation(saturation) + + // Combine all matrices + colorMatrix.postConcat(brightnessMatrix) + colorMatrix.postConcat(contrastMatrix) + colorMatrix.postConcat(saturationMatrix) + + val resultBitmap = Bitmap.createBitmap( + rotatedBitmap.width, rotatedBitmap.height, Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(resultBitmap) + val paint = Paint().apply { + colorFilter = ColorMatrixColorFilter(colorMatrix) + } + canvas.drawBitmap(rotatedBitmap, 0f, 0f, paint) + + if (rotatedBitmap != bitmap) { + rotatedBitmap.recycle() + } + + return resultBitmap + } + + private fun saveImage() { + val bitmap = currentState.editedBitmap ?: return + updateState { it.copy(isSaving = true) } + + !Single.fromCallable { + val stream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) + stream.toByteArray() + } + .subscribeOn(schedulersProvider.io) + .flatMapCompletable { bytes -> + io.reactivex.rxjava3.core.Completable.fromAction { + mediaStoreGateway.exportToFile( + fileName = "pdai_edited_${System.currentTimeMillis()}", + content = bytes, + ) + } + } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + errorLog(t) + updateState { it.copy(isSaving = false) } + }, + onComplete = { + updateState { it.copy(isSaving = false, hasChanges = false) } + emitEffect(ImageEditorEffect.SavedSuccessfully) + mainRouter.navigateBack() + } + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryEffect.kt new file mode 100644 index 000000000..7725a6ada --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryEffect.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import android.net.Uri +import com.shifthackz.android.core.mvi.MviEffect +import java.io.File + +sealed interface GalleryEffect : MviEffect { + + data object Refresh : GalleryEffect + + data class Share(val zipFile: File) : GalleryEffect + + data class OpenUri(val uri: Uri) : GalleryEffect + + data object AllImagesSavedToGallery : GalleryEffect + + data object SelectionImagesSavedToGallery : GalleryEffect +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryExporter.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryExporter.kt new file mode 100644 index 000000000..7cac159eb --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryExporter.kt @@ -0,0 +1,40 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter.Input +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter.Output +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.usecase.gallery.GetAllGalleryUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsUseCase +import dev.minios.pdaiv1.presentation.utils.FileSavableExporter +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import java.io.File + +class GalleryExporter( + override val fileProviderDescriptor: FileProviderDescriptor, + private val getGalleryItemsUseCase: GetGalleryItemsUseCase, + private val getAllGalleryUseCase: GetAllGalleryUseCase, + private val base64ToBitmapConverter: Base64ToBitmapConverter, + private val schedulersProvider: SchedulersProvider, +) : FileSavableExporter.BmpToFile, FileSavableExporter.FilesToZip { + + operator fun invoke(ids: List? = null): Single { + val chain = ids?.let(getGalleryItemsUseCase::invoke) ?: getAllGalleryUseCase() + return chain + .subscribeOn(schedulersProvider.io) + .flatMapObservable { Observable.fromIterable(it) } + .map { aiDomain -> aiDomain to Input(aiDomain.image) } + .flatMapSingle { (aiDomain, input) -> + base64ToBitmapConverter(input).map { out -> aiDomain to out } + } + .flatMapSingle(::saveBitmapToFileImpl) + .toList() + .flatMap(::saveFilesToZip) + } + + private fun saveBitmapToFileImpl(data: Pair) = + saveBitmapToFile(data.first.hashCode().toString(), data.second.bitmap) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryIntent.kt new file mode 100644 index 000000000..212a284aa --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryIntent.kt @@ -0,0 +1,83 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import android.net.Uri +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface GalleryIntent : MviIntent { + + sealed interface Export : GalleryIntent { + + enum class All : Export { + Request, Confirm; + } + + enum class Selection : Export { + Request, Confirm; + } + } + + sealed interface Delete : GalleryIntent { + + enum class All : Export { + Request, Confirm; + } + + enum class AllUnliked : Delete { + Request, Confirm; + } + + enum class Selection : Delete { + Request, Confirm; + } + } + + // Bulk actions for selected items + data object LikeSelection : GalleryIntent + data object HideSelection : GalleryIntent + + enum class Dropdown : GalleryIntent { + Toggle, Show, Close; + } + + data object DismissDialog : GalleryIntent + + data class OpenItem(val id: Long, val index: Int) : GalleryIntent + + data object CloseItem : GalleryIntent + + data object ClearScrollPosition : GalleryIntent + + data class OpenMediaStoreFolder(val uri: Uri) : GalleryIntent + + sealed interface SaveToGallery : GalleryIntent { + enum class All : SaveToGallery { + Request, Confirm; + } + enum class Selection : SaveToGallery { + Request, Confirm; + } + } + + data class Drawer(val intent: DrawerIntent) : GalleryIntent + + data class ChangeSelectionMode(val flag: Boolean) : GalleryIntent + + data object UnselectAll : GalleryIntent + + data class ToggleItemSelection(val id: Long) : GalleryIntent + + sealed interface GridZoom : GalleryIntent { + data object ZoomIn : GridZoom // Less columns, bigger thumbnails + data object ZoomOut : GridZoom // More columns, smaller thumbnails + } + + sealed interface DragSelection : GalleryIntent { + data class Start(val itemId: Long) : DragSelection + data class UpdateRange(val fromIndex: Int, val toIndex: Int, val itemIds: List) : DragSelection + data object End : DragSelection + } + + // Immich-style lazy loading + data class LoadThumbnails(val ids: List) : GalleryIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryPagingSource.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryPagingSource.kt new file mode 100644 index 000000000..dbdacdddc --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryPagingSource.kt @@ -0,0 +1,97 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import android.graphics.Bitmap +import androidx.paging.PagingSource +import androidx.paging.PagingState +import androidx.paging.rxjava3.RxPagingSource +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.imageprocessing.ThumbnailGenerator +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCase +import dev.minios.pdaiv1.presentation.utils.Constants +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single + +typealias GalleryPagedResult = PagingSource.LoadResult + +/** + * Data class for passing image info through the processing pipeline + */ +private data class ImageInfo( + val id: Long, + val hidden: Boolean, + val base64Image: String, +) + +/** + * Immich-style paging source that loads thumbnails in parallel. + * Uses high concurrency and processes items as they complete, + * maintaining order via sorting after parallel loading. + */ +class GalleryPagingSource( + private val getGenerationResultPagedUseCase: GetGenerationResultPagedUseCase, + private val thumbnailGenerator: ThumbnailGenerator, + private val schedulersProvider: SchedulersProvider, +) : RxPagingSource() { + + override fun getRefreshKey(state: PagingState) = FIRST_KEY + + override fun loadSingle(params: LoadParams) = loadSingleImpl(params) + + private fun loadSingleImpl(params: LoadParams): Single { + val pageSize = params.loadSize + val pageNext = params.key ?: FIRST_KEY + return getGenerationResultPagedUseCase( + limit = pageSize, + offset = pageNext * Constants.PAGINATION_PAYLOAD_SIZE, + ) + .subscribeOn(schedulersProvider.io) // Use IO for database access + .flatMapObservable { list -> Observable.fromIterable(list) } + .map { ai -> ImageInfo(ai.id, ai.hidden, ai.image) } + // High concurrency parallel thumbnail loading (like Immich) + .flatMap( + { info: ImageInfo -> + thumbnailGenerator.generate( + id = info.id.toString(), + base64ImageString = info.base64Image, + ) + .map { bitmap -> Triple(info.id, info.hidden, bitmap) } + .toObservable() + .subscribeOn(schedulersProvider.computation) // Each thumbnail on computation + }, + MAX_CONCURRENT_LOADS, + ) + .map { triple -> mapToUi(triple) } + .toList() + // Sort by id descending to maintain order after parallel loading + .map { payload -> payload.sortedByDescending { item -> item.id } } + .map { payload -> + Wrapper( + LoadResult.Page( + data = payload, + prevKey = if (pageNext == FIRST_KEY) null else pageNext - 1, + nextKey = if (payload.isEmpty()) null else pageNext + 1, + ) + ) + } + .onErrorReturn { t: Throwable -> + errorLog(t) + Wrapper(LoadResult.Error(t)) + } + .map { wrapper -> wrapper.loadResult } + } + + private data class Wrapper(val loadResult: GalleryPagedResult) + + private fun mapToUi(data: Triple) = GalleryGridItemUi( + id = data.first, + bitmap = data.third, + hidden = data.second, + ) + + companion object { + const val FIRST_KEY = 0 + // Very high concurrency for fast gallery loading (Immich uses parallel streams) + const val MAX_CONCURRENT_LOADS = 32 + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryScreen.kt new file mode 100644 index 000000000..3ae4addc9 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryScreen.kt @@ -0,0 +1,1283 @@ +@file:OptIn( + ExperimentalMaterial3Api::class, + ExperimentalFoundationApi::class, + ExperimentalSharedTransitionApi::class, +) + +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import android.content.Intent +import android.provider.DocumentsContract +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Checklist +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FileOpen +import androidx.compose.material.icons.filled.HeartBroken +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.Save +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalMinimumInteractiveComponentSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.BlurEffect +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.foundation.gestures.calculateZoom +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import dev.minios.pdaiv1.presentation.screen.gallery.list.selection.rememberDragSelectionState +import dev.minios.pdaiv1.presentation.screen.gallery.list.selection.DragSelectionUtils +import dev.minios.pdaiv1.presentation.components.DraggableScrollbar +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.toUpperCase +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.extensions.items +import dev.minios.pdaiv1.core.extensions.shake +import dev.minios.pdaiv1.core.extensions.shimmer +import dev.minios.pdaiv1.core.sharing.shareFile +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.presentation.R +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.widget.work.BackgroundWorkWidget +import com.shifthackz.android.core.mvi.MviComponent +import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject +import dev.minios.pdaiv1.presentation.navigation.LocalSetHideBottomNavigation +import dev.minios.pdaiv1.presentation.navigation.LocalSharedTransitionScope +import dev.minios.pdaiv1.presentation.navigation.LocalAnimatedVisibilityScope +import dev.minios.pdaiv1.presentation.navigation.galleryImageSharedKey +import dev.minios.pdaiv1.presentation.screen.gallery.detail.GalleryDetailScreen +import androidx.compose.runtime.CompositionLocalProvider +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun GalleryScreen() { + val viewModel = koinViewModel() + val context = LocalContext.current + val fileProviderDescriptor: FileProviderDescriptor = koinInject() + MviComponent( + viewModel = viewModel, + processEffect = { effect -> + when (effect) { + is GalleryEffect.OpenUri -> with(Intent(Intent.ACTION_VIEW)) { + setDataAndType(effect.uri, DocumentsContract.Document.MIME_TYPE_DIR) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + context.startActivity(this) + } + + is GalleryEffect.Share -> context.shareFile( + file = effect.zipFile, + fileProviderPath = fileProviderDescriptor.providerPath, + fileMimeType = Constants.MIME_TYPE_ZIP, + ) + + GalleryEffect.Refresh -> { + // Immich-style: IDs are reloaded in ViewModel's loadAllIds() + // Thumbnail cache is preserved, will reload thumbnails as needed + } + + GalleryEffect.AllImagesSavedToGallery -> { + Toast.makeText( + context, + context.getString(LocalizationR.string.gallery_save_all_success), + Toast.LENGTH_SHORT, + ).show() + } + + GalleryEffect.SelectionImagesSavedToGallery -> { + Toast.makeText( + context, + context.getString(LocalizationR.string.gallery_save_selection_success), + Toast.LENGTH_SHORT, + ).show() + } + } + }, + ) { state, intentHandler -> + BackHandler(state.selectionMode || state.selectedItemId != null) { + when { + state.selectedItemId != null -> intentHandler(GalleryIntent.CloseItem) + state.selectionMode -> intentHandler(GalleryIntent.ChangeSelectionMode(false)) + } + } + GalleryScreenContent( + state = state, + processIntent = intentHandler, + ) + } +} + +@Composable +fun GalleryScreenContent( + modifier: Modifier = Modifier, + state: GalleryState, + processIntent: (GalleryIntent) -> Unit = {}, +) { + val listState = rememberLazyGridState() + + // Check if we have a scroll target on first composition (returning from detail) + val hasScrollTarget = state.scrollToItemIndex != null + var isRestoringScroll by remember { mutableStateOf(hasScrollTarget) } + + // Pinch-to-zoom gesture state + var accumulatedZoom by remember { mutableStateOf(1f) } + val zoomThreshold = 0.3f + + // Drag selection state + val dragSelectionState = rememberDragSelectionState() + var dragStartIndex by remember { mutableIntStateOf(-1) } + var dragCurrentIndex by remember { mutableIntStateOf(-1) } + + // UI visibility state for scroll hide/show behavior + // toolbarOffsetHeightPx: 0f = fully visible, -topBarHeightPx = fully hidden + var toolbarOffsetHeightPx by remember { mutableFloatStateOf(0f) } + val density = LocalDensity.current + val topBarContentHeight = 72.dp // Height of top bar content (without status bar) + val statusBarHeightDp = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val topBarHeight = topBarContentHeight + statusBarHeightDp + val bottomNavHeight = 80.dp // Approximate height of bottom navigation + val topBarHeightPx = with(density) { topBarHeight.toPx() } + + // Get callback to hide/show bottom navigation in HomeNavigationScreen + val setHideBottomNavigation = LocalSetHideBottomNavigation.current + + // NestedScrollConnection for hiding/showing toolbar - standard Android pattern + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.y + val newOffset = toolbarOffsetHeightPx + delta + toolbarOffsetHeightPx = newOffset.coerceIn(-topBarHeightPx, 0f) + return Offset.Zero // Don't consume scroll, let grid handle it + } + } + } + + // Update bottom navigation visibility when toolbar offset changes + LaunchedEffect(toolbarOffsetHeightPx) { + val isHidden = toolbarOffsetHeightPx < -topBarHeightPx / 2 + setHideBottomNavigation?.invoke(isHidden) + } + + // Show toolbar when at top of list + LaunchedEffect(listState) { + snapshotFlow { + listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset < 50 + }.collect { isAtTop -> + if (isAtTop) { + toolbarOffsetHeightPx = 0f + } + } + } + + // Calculate if UI should be shown (for selection mode override) + // In selection mode, always show UI regardless of offset + val effectiveToolbarOffset = if (state.selectionMode || state.selectedItemId != null) 0f else toolbarOffsetHeightPx + + // Reset toolbar offset when returning from detail + LaunchedEffect(state.selectedItemId) { + if (state.selectedItemId == null) { + toolbarOffsetHeightPx = 0f + } + } + + // Scroll to saved position - Immich-style: use allIds since all IDs are loaded upfront + LaunchedEffect(Unit) { + val targetIndex = state.scrollToItemIndex + if (targetIndex == null) { + isRestoringScroll = false + return@LaunchedEffect + } + + // Wait for IDs to load (they load instantly at startup) + while (state.allIds.isEmpty() && state.isInitialLoading) { + delay(50) + } + + // With Immich-style loading, all IDs are available immediately + // Just scroll to target - no need to wait for items to load + if (targetIndex < state.allIds.size) { + listState.scrollToItem(targetIndex) + } + isRestoringScroll = false + processIntent(GalleryIntent.ClearScrollPosition) + } + + // Show shimmer while restoring + val showShimmerForScrollRestore = isRestoringScroll + + // Immich-style: check empty based on allIds after initial load completes + val emptyStatePredicate: () -> Boolean = { + !state.isInitialLoading && state.allIds.isEmpty() + } + + // Trigger thumbnail loading for visible items + LaunchedEffect(listState, state.allIds) { + snapshotFlow { + val layoutInfo = listState.layoutInfo + layoutInfo.visibleItemsInfo.mapNotNull { item -> + // Only consider actual grid items (not spacers) + if (item.index < state.allIds.size) { + state.allIds.getOrNull(item.index) + } else null + } + }.collect { visibleIds -> + if (visibleIds.isNotEmpty()) { + // Also prefetch some items before and after visible range + val firstVisible = listState.firstVisibleItemIndex + val prefetchRange = 30 // Load 30 items before/after visible + val startIndex = (firstVisible - prefetchRange).coerceAtLeast(0) + val endIndex = (firstVisible + listState.layoutInfo.visibleItemsInfo.size + prefetchRange) + .coerceAtMost(state.allIds.size) + val idsToLoad = state.allIds.subList(startIndex, endIndex) + processIntent(GalleryIntent.LoadThumbnails(idsToLoad)) + } + } + } + + Box(modifier) { + Scaffold( + modifier = Modifier.fillMaxSize(), + // No topBar/bottomBar - they're rendered as overlays to avoid layout jumps + ) { _ -> + when { + emptyStatePredicate() -> GalleryEmptyState(Modifier.fillMaxSize()) + + // Immich-style: show shimmer while initial IDs are loading + state.isInitialLoading -> LazyVerticalGrid( + modifier = Modifier + .fillMaxSize(), + columns = GridCells.Fixed(state.grid.size), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp, bottom = bottomNavHeight + 32.dp, top = topBarHeight), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + val max = when (state.grid) { + Grid.Fixed1 -> 3 + Grid.Fixed2 -> 6 + Grid.Fixed3 -> 12 + Grid.Fixed4 -> 20 + Grid.Fixed5 -> 30 + Grid.Fixed6 -> 42 + } + repeat(max) { + item(it) { + GalleryUiItemShimmer() + } + } + } + + else -> Box( + modifier = Modifier + .fillMaxSize() + .pointerInput(state.grid) { + awaitEachGesture { + // Wait for first pointer - use pass Final so children get events first + awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Final) + do { + val event = awaitPointerEvent(pass = PointerEventPass.Final) + // Only handle pinch-to-zoom with 2+ pointers + if (event.changes.size >= 2) { + val zoom = event.calculateZoom() + accumulatedZoom *= zoom + + when { + accumulatedZoom > 1f + zoomThreshold -> { + accumulatedZoom = 1f + processIntent(GalleryIntent.GridZoom.ZoomIn) + } + accumulatedZoom < 1f - zoomThreshold -> { + accumulatedZoom = 1f + processIntent(GalleryIntent.GridZoom.ZoomOut) + } + } + + event.changes.forEach { it.consume() } + } + } while (event.changes.any { it.pressed }) + accumulatedZoom = 1f + } + } + ) { + // Calculate item size for drag selection hit testing + val contentPaddingPx = with(density) { 16.dp.toPx() } + val spacingPx = with(density) { 16.dp.toPx() } + var gridWidth by remember { mutableFloatStateOf(0f) } + var gridHeight by remember { mutableFloatStateOf(0f) } + val itemSizePx = remember(gridWidth, state.grid.size) { + if (gridWidth > 0 && state.grid.size > 0) { + (gridWidth - contentPaddingPx * 2 - spacingPx * (state.grid.size - 1)) / state.grid.size + } else 0f + } + val topPaddingPx = with(density) { topBarHeight.toPx() } + val itemWithSpacingPx = itemSizePx + spacingPx + + // Track which items are being dragged for proper deselection + var draggedIds by remember { mutableStateOf(setOf()) } + + // Auto-scroll state + val dragScope = rememberCoroutineScope() + var autoScrollJob by remember { mutableStateOf(null) } + var currentDragPosition by remember { mutableStateOf(null) } + + // Helper function to calculate item index from position + fun calculateIndexFromPosition(offset: androidx.compose.ui.geometry.Offset): Int { + if (itemSizePx <= 0 || itemWithSpacingPx <= 0) return -1 + + // Y position adjusted for top content padding + val adjustedY = offset.y - topPaddingPx + + val col = ((offset.x - contentPaddingPx) / itemWithSpacingPx).toInt() + .coerceIn(0, state.grid.size - 1) + + // Calculate which row in the visible area + val rowInView = (adjustedY / itemWithSpacingPx).toInt() + + // Account for scroll: firstVisibleItemIndex and scroll offset + val firstVisibleRow = listState.firstVisibleItemIndex / state.grid.size + // Scroll offset is how much the first visible row is scrolled out (positive = scrolled up) + // We need to add fractional rows based on scroll offset + val scrollOffsetRows = listState.firstVisibleItemScrollOffset / itemWithSpacingPx + + // If adjustedY is negative, we're in the content padding area above first item + val actualRow = if (adjustedY < 0) { + // Above visible content, clamp to first visible row + firstVisibleRow + } else { + // Add scroll offset to account for partial scroll + (firstVisibleRow.toFloat() + rowInView.toFloat() + scrollOffsetRows).toInt() + } + + return (actualRow * state.grid.size + col).coerceIn(0, state.allIds.size - 1) + } + + // Auto-scroll effect - runs when drag is active and position is at edges + LaunchedEffect(dragSelectionState.isActive, currentDragPosition) { + if (!dragSelectionState.isActive || currentDragPosition == null) { + autoScrollJob?.cancel() + return@LaunchedEffect + } + + val pos = currentDragPosition!! + val edgeThreshold = gridHeight * 0.10f // 10% of height + + val scrollDirection = when { + pos.y < edgeThreshold -> -1 // Scroll up + pos.y > gridHeight - edgeThreshold -> 1 // Scroll down + else -> 0 + } + + if (scrollDirection != 0) { + autoScrollJob?.cancel() + autoScrollJob = dragScope.launch { + while (true) { + val scrollAmount = if (scrollDirection > 0) 150f else -150f + listState.dispatchRawDelta(scrollAmount) + + // Update selection after scroll - same position now points to different index + val currentIndex = calculateIndexFromPosition(pos) + if (currentIndex >= 0 && currentIndex != dragCurrentIndex) { + val range = DragSelectionUtils.calculateSelectedRange(dragStartIndex, currentIndex) + val newDraggedIds = range.mapNotNull { idx -> + state.allIds.getOrNull(idx) + }.toSet() + + // Deselect items no longer in range + val toDeselect = draggedIds - newDraggedIds + toDeselect.forEach { id -> + processIntent(GalleryIntent.ToggleItemSelection(id)) + } + + // Select items newly in range + val toSelect = newDraggedIds - draggedIds + toSelect.forEach { id -> + processIntent(GalleryIntent.ToggleItemSelection(id)) + } + + draggedIds = newDraggedIds + dragCurrentIndex = currentIndex + } + + delay(50) + } + } + } else { + autoScrollJob?.cancel() + } + } + + LazyVerticalGrid( + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection) + .onGloballyPositioned { coordinates -> + gridWidth = coordinates.size.width.toFloat() + gridHeight = coordinates.size.height.toFloat() + } + .pointerInput(state.grid.size, state.allIds.size) { + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + if (itemSizePx <= 0) { + return@detectDragGesturesAfterLongPress + } + + val index = calculateIndexFromPosition(offset) + if (index < 0) { + return@detectDragGesturesAfterLongPress + } + + dragStartIndex = index + dragCurrentIndex = index + dragSelectionState.startDrag(offset, index) + currentDragPosition = offset + + // Enter selection mode + if (!state.selectionMode) { + processIntent(GalleryIntent.ChangeSelectionMode(true)) + } + + // Select first item + val id = state.allIds.getOrNull(index) + if (id != null) { + draggedIds = setOf(id) + // Toggle selection for first item + processIntent(GalleryIntent.ToggleItemSelection(id)) + } + }, + onDrag = { change, _ -> + if (!dragSelectionState.isActive || itemSizePx <= 0) return@detectDragGesturesAfterLongPress + change.consume() + + val offset = change.position + currentDragPosition = offset + + val currentIndex = calculateIndexFromPosition(offset) + if (currentIndex < 0) return@detectDragGesturesAfterLongPress + + if (currentIndex != dragCurrentIndex) { + // Calculate range between anchor and current + val range = DragSelectionUtils.calculateSelectedRange(dragStartIndex, currentIndex) + + // Collect new set of IDs in range + val newDraggedIds = range.mapNotNull { idx -> + state.allIds.getOrNull(idx) + }.toSet() + + // Deselect items no longer in range + val toDeselect = draggedIds - newDraggedIds + toDeselect.forEach { id -> + processIntent(GalleryIntent.ToggleItemSelection(id)) + } + + // Select items newly in range + val toSelect = newDraggedIds - draggedIds + toSelect.forEach { id -> + processIntent(GalleryIntent.ToggleItemSelection(id)) + } + + draggedIds = newDraggedIds + dragCurrentIndex = currentIndex + } + dragSelectionState.updateDrag(offset) + }, + onDragEnd = { + autoScrollJob?.cancel() + currentDragPosition = null + dragSelectionState.endDrag() + dragStartIndex = -1 + dragCurrentIndex = -1 + draggedIds = emptySet() + }, + onDragCancel = { + autoScrollJob?.cancel() + currentDragPosition = null + dragSelectionState.endDrag() + dragStartIndex = -1 + dragCurrentIndex = -1 + draggedIds = emptySet() + } + ) + }, + columns = GridCells.Fixed(state.grid.size), + contentPadding = PaddingValues( + start = 16.dp, + end = 16.dp, + bottom = bottomNavHeight + 32.dp, + top = topBarHeight + ), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + state = listState, + ) { + // Immich-style: use allIds for count, build items from thumbnailCache + items( + count = state.allIds.size, + key = { index -> state.allIds.getOrNull(index) ?: index.toLong() }, + ) { index -> + val id = state.allIds[index] + val bitmap = state.thumbnailCache[id] + val hidden = state.hiddenIds.contains(id) + val liked = state.likedIds.contains(id) + val blurHash = state.blurHashCache[id] ?: "" + val item = GalleryGridItemUi(id = id, bitmap = bitmap, hidden = hidden, liked = liked, blurHash = blurHash) + val selected = state.selection.contains(id) + GalleryUiItem( + modifier = Modifier + .animateItem(tween(300)) + .then( + if (state.selectionMode && !selected) { + Modifier.shake( + enabled = true, + animationDurationMillis = 188, + animationStartOffset = (id % 320).toInt(), + ) + } else Modifier + ), + item = item, + selectionMode = state.selectionMode, + checked = selected, + onCheckedChange = { + processIntent(GalleryIntent.ToggleItemSelection(id)) + }, + onLongClick = { + // Long press is handled by drag selection gesture on grid level + // Only act if drag selection is not active (accessibility fallback) + if (!dragSelectionState.isActive && !state.selectionMode) { + processIntent(GalleryIntent.ChangeSelectionMode(true)) + processIntent(GalleryIntent.ToggleItemSelection(id)) + } + }, + onClick = { + processIntent(GalleryIntent.OpenItem(id, index)) + }, + ) + } + items(2) { Spacer(modifier = Modifier.height(32.dp)) } + } + // Shimmer overlay while restoring scroll position + AnimatedVisibility( + visible = showShimmerForScrollRestore, + enter = fadeIn(), + exit = fadeOut(), + ) { + LazyVerticalGrid( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + columns = GridCells.Fixed(state.grid.size), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp, bottom = bottomNavHeight + 32.dp, top = topBarHeight), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + val max = when (state.grid) { + Grid.Fixed1 -> 3 + Grid.Fixed2 -> 6 + Grid.Fixed3 -> 12 + Grid.Fixed4 -> 20 + Grid.Fixed5 -> 30 + Grid.Fixed6 -> 42 + } + repeat(max) { + item(it) { + GalleryUiItemShimmer() + } + } + } + } + + // Draggable scrollbar for fast navigation (Immich-style) + DraggableScrollbar( + lazyGridState = listState, + totalItems = state.allIds.size, + columns = state.grid.size, + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(top = topBarHeight, bottom = bottomNavHeight + 32.dp, end = 4.dp), + ) + + // Multi-select indicator (Immich-style) - shows count and close button + AnimatedVisibility( + visible = state.selectionMode, + enter = fadeIn() + slideInVertically { -it }, + exit = fadeOut() + slideOutVertically { -it }, + modifier = Modifier + .align(Alignment.TopStart) + .padding(start = 16.dp, top = topBarHeight + 8.dp), + ) { + androidx.compose.material3.ElevatedButton( + onClick = { processIntent(GalleryIntent.ChangeSelectionMode(false)) }, + colors = androidx.compose.material3.ButtonDefaults.elevatedButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Cancel selection", + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.onPrimary, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = state.selection.size.toString(), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + } + } + } + + // TopBar as overlay - slides up/down based on scroll offset + Column( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + .graphicsLayer { + translationY = effectiveToolbarOffset + } + .background(MaterialTheme.colorScheme.surface) + .statusBarsPadding() + ) { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent, + ), + navigationIcon = { + AnimatedContent( + targetState = state.selectionMode, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + label = "main_nav_icon_animation", + ) { isInSelectionMode -> + IconButton( + onClick = { + val intent = if (isInSelectionMode) { + GalleryIntent.ChangeSelectionMode(false) + } else { + GalleryIntent.Drawer(DrawerIntent.Open) + } + processIntent(intent) + }, + ) { + Icon( + imageVector = if (isInSelectionMode) { + Icons.Default.Close + } else { + Icons.Default.Menu + }, + contentDescription = if (isInSelectionMode) "Close" else "Menu", + ) + } + } + }, + title = { + AnimatedVisibility( + visible = !state.selectionMode, + enter = fadeIn(), + exit = fadeOut(), + ) { + Text( + text = stringResource(id = LocalizationR.string.title_gallery), + style = MaterialTheme.typography.headlineMedium, + ) + } + }, + actions = { + AnimatedContent( + targetState = state.selectionMode, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + label = "action_nav_icon_animation", + ) { isInSelectionMode -> + if (isInSelectionMode) { + AnimatedVisibility( + visible = state.selection.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + Row { + IconButton( + onClick = { + processIntent(GalleryIntent.LikeSelection) + }, + ) { + Icon( + imageVector = Icons.Default.Favorite, + contentDescription = "Like", + ) + } + IconButton( + onClick = { + processIntent(GalleryIntent.HideSelection) + }, + ) { + Icon( + imageVector = Icons.Default.VisibilityOff, + contentDescription = "Hide", + ) + } + IconButton( + onClick = { + processIntent(GalleryIntent.Delete.Selection.Request) + }, + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Delete", + ) + } + IconButton( + onClick = { + processIntent(GalleryIntent.SaveToGallery.Selection.Request) + }, + ) { + Icon( + imageVector = Icons.Default.Save, + contentDescription = "Save to Gallery", + ) + } + IconButton( + onClick = { + processIntent(GalleryIntent.Export.Selection.Request) + }, + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.ic_share), + contentDescription = "Export", + colorFilter = ColorFilter.tint(LocalContentColor.current), + ) + } + } + } + } else { + AnimatedVisibility( + visible = state.allIds.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + IconButton( + onClick = { + processIntent(GalleryIntent.Dropdown.Toggle) + }, + ) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = "Dropdown", + ) + } + } + } + } + DropdownMenu( + expanded = state.dropdownMenuShow, + onDismissRequest = { processIntent(GalleryIntent.Dropdown.Close) }, + containerColor = MaterialTheme.colorScheme.background, + ) { + DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Default.Checklist, + contentDescription = "Dropdown", + tint = LocalContentColor.current, + ) + }, + text = { + Text( + text = stringResource( + id = LocalizationR.string.gallery_menu_selection_mode, + ), + ) + }, + onClick = { + processIntent(GalleryIntent.Dropdown.Close) + processIntent(GalleryIntent.ChangeSelectionMode(true)) + }, + ) + if (state.mediaStoreInfo.isNotEmpty) DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Default.FileOpen, + contentDescription = "Browse", + tint = LocalContentColor.current, + ) + }, + text = { + Text( + text = stringResource(id = LocalizationR.string.browse) + ) + }, + onClick = { + processIntent(GalleryIntent.Dropdown.Close) + state.mediaStoreInfo.folderUri?.let { + processIntent(GalleryIntent.OpenMediaStoreFolder(it)) + } + }, + ) + DropdownMenuItem( + leadingIcon = { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.ic_share), + contentDescription = "Export", + colorFilter = ColorFilter.tint(LocalContentColor.current), + ) + }, + text = { + Text( + text = stringResource(id = LocalizationR.string.gallery_menu_export_all) + ) + }, + onClick = { + processIntent(GalleryIntent.Dropdown.Close) + processIntent(GalleryIntent.Export.All.Request) + }, + ) + DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Default.Save, + contentDescription = "Save to Gallery", + ) + }, + text = { + Text( + text = stringResource(id = LocalizationR.string.gallery_menu_save_all) + ) + }, + onClick = { + processIntent(GalleryIntent.Dropdown.Close) + processIntent(GalleryIntent.SaveToGallery.All.Request) + }, + ) + DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Delete", + ) + }, + text = { + Text( + text = stringResource(id = LocalizationR.string.gallery_menu_delete_all), + ) + }, + onClick = { + processIntent(GalleryIntent.Dropdown.Close) + processIntent(GalleryIntent.Delete.All.Request) + }, + ) + DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Default.HeartBroken, + contentDescription = "Delete unliked", + ) + }, + text = { + Text( + text = stringResource(id = LocalizationR.string.gallery_menu_delete_unliked), + ) + }, + onClick = { + processIntent(GalleryIntent.Dropdown.Close) + processIntent(GalleryIntent.Delete.AllUnliked.Request) + }, + ) + } + }, + windowInsets = WindowInsets(0, 0, 0, 0), + ) + } + + // Immich-style: Gallery Detail as overlay with shared element transitions + // This keeps the gallery visible underneath during swipe-to-dismiss animation + // Remember the last itemId to show during exit animation + var lastItemId by remember { mutableStateOf(null) } + if (state.selectedItemId != null) { + lastItemId = state.selectedItemId + } + + val sharedTransitionScope = LocalSharedTransitionScope.current + sharedTransitionScope?.let { transitionScope -> + with(transitionScope) { + AnimatedVisibility( + visible = state.selectedItemId != null, + enter = fadeIn(animationSpec = tween(300)), + exit = fadeOut(animationSpec = tween(300)), + ) { + lastItemId?.let { itemId -> + CompositionLocalProvider( + LocalAnimatedVisibilityScope provides this@AnimatedVisibility + ) { + key(itemId) { + GalleryDetailOverlay( + itemId = itemId, + onDismiss = { processIntent(GalleryIntent.CloseItem) }, + ) + } + } + } + } + } + } ?: run { + // Fallback without shared element transitions + state.selectedItemId?.let { itemId -> + key(itemId) { + GalleryDetailOverlay( + itemId = itemId, + onDismiss = { processIntent(GalleryIntent.CloseItem) }, + ) + } + } + } + + ModalRenderer(screenModal = state.screenModal) { + (it as? GalleryIntent)?.let(processIntent::invoke) + } + } +} + +@Composable +fun GalleryUiItem( + modifier: Modifier = Modifier, + item: GalleryGridItemUi, + checked: Boolean = false, + onClick: (GalleryGridItemUi) -> Unit = {}, + onLongClick: () -> Unit = {}, + onCheckedChange: (Boolean) -> Unit = {}, + selectionMode: Boolean = false, +) { + val shape = RoundedCornerShape(12.dp) + val borderColor by animateColorAsState( + targetValue = if (selectionMode && checked) { + MaterialTheme.colorScheme.primary + } else { + Color.Transparent + }, + label = "border_color", + ) + + // Get shared transition scope for Immich-style hero animation + val sharedTransitionScope = LocalSharedTransitionScope.current + + Box( + modifier = modifier.clip(shape), + ) { + val imageModifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .border( + width = 4.dp, + color = borderColor, + shape = shape, + ) + .then( + if (!item.hidden) { + Modifier + } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + Modifier.graphicsLayer { + clip = true + renderEffect = BlurEffect( + radiusX = 100f, + radiusY = 100f, + edgeTreatment = TileMode.Decal, + ) + } + } else { + // Fallback for Android < 12 + Modifier.graphicsLayer { alpha = 0.1f } + } + ) + .combinedClickable( + onLongClick = null, // Long press handled by drag selection on grid level + onClick = { + if (!selectionMode) { + onClick(item) + } else { + onCheckedChange(!checked) + } + }, + ) + + // Apply shared element modifier when AnimatedVisibilityScope is available + // This works when navigating from GalleryFull route in main NavHost + val animatedVisibilityScope = LocalAnimatedVisibilityScope.current + val finalModifier = if (sharedTransitionScope != null && animatedVisibilityScope != null) { + with(sharedTransitionScope) { + imageModifier.sharedElement( + sharedContentState = rememberSharedContentState(key = galleryImageSharedKey(item.id)), + animatedVisibilityScope = animatedVisibilityScope, + ) + } + } else { + imageModifier + } + + // Show placeholder if bitmap not loaded yet (Immich-style lazy loading) + val imageBitmap = item.imageBitmap + if (imageBitmap != null) { + Image( + modifier = finalModifier, + bitmap = imageBitmap, + contentScale = ContentScale.Crop, + contentDescription = "gallery_item", + ) + } else if (item.blurHash.isNotEmpty()) { + // BlurHash placeholder + val blurHashBitmap = remember(item.blurHash) { + dev.minios.pdaiv1.core.imageprocessing.blurhash.BlurHashDecoder.decodeStatic(item.blurHash, 32, 32) + } + if (blurHashBitmap != null) { + Image( + modifier = finalModifier, + bitmap = blurHashBitmap.asImageBitmap(), + contentScale = ContentScale.Crop, + contentDescription = "gallery_item_placeholder", + ) + } else { + // Shimmer placeholder if BlurHash decode failed + Box( + modifier = finalModifier.shimmer() + ) + } + } else { + // Shimmer placeholder while loading (no BlurHash available) + Box( + modifier = finalModifier.shimmer() + ) + } + if (item.hidden) { + // Overlay for Android < 12 (no blur support) + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) { + Box( + modifier = Modifier + .matchParentSize() + .background(Color.Black.copy(alpha = 0.85f)) + ) + } + Icon( + modifier = Modifier + .size(28.dp) + .align(Alignment.Center), + imageVector = Icons.Default.VisibilityOff, + contentDescription = "hidden", + tint = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + MaterialTheme.colorScheme.primary + } else { + Color.White.copy(alpha = 0.6f) + }, + ) + } + // Like indicator (top-right corner) + if (item.liked && !selectionMode) { + Icon( + modifier = Modifier + .size(20.dp) + .padding(4.dp) + .align(Alignment.TopEnd), + imageVector = Icons.Default.Favorite, + contentDescription = "liked", + tint = Color.Red, + ) + } + if (selectionMode) { + val checkBoxShape = RoundedCornerShape(4.dp) + Box( + modifier = Modifier + .padding(8.dp) + .size(20.dp) + .align(Alignment.TopEnd) + .clip(checkBoxShape) + .background( + color = MaterialTheme.colorScheme.primary.copy(alpha = 0.35f), + shape = checkBoxShape, + ), + ) { + CompositionLocalProvider( + LocalMinimumInteractiveComponentSize provides Dp.Unspecified, + ) { + Checkbox( + checked = checked, + onCheckedChange = onCheckedChange, + colors = CheckboxDefaults.colors( + uncheckedColor = MaterialTheme.colorScheme.primary, + ), + ) + } + } + } + } +} + +@Composable +private fun GalleryUiItemShimmer() { + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .clip(RoundedCornerShape(12.dp)) + .shimmer() + ) +} + +@Composable +private fun GalleryEmptyState(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.Center, + ) { + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = stringResource(id = LocalizationR.string.gallery_empty_title), + fontSize = 20.sp, + ) + Text( + modifier = Modifier + .padding(top = 16.dp) + .padding(horizontal = 16.dp) + .align(Alignment.CenterHorizontally), + text = stringResource(id = LocalizationR.string.gallery_empty_sub_title), + textAlign = TextAlign.Center, + ) + } +} + +/** + * Immich-style overlay for gallery detail view. + * Shows the detail screen as an overlay on top of the gallery grid, + * allowing the gallery to remain visible during swipe-to-dismiss animation. + */ +@Composable +private fun GalleryDetailOverlay( + itemId: Long, + onDismiss: () -> Unit, +) { + // Hide bottom navigation bar when detail overlay is shown + val setHideBottomNavigation = LocalSetHideBottomNavigation.current + + LaunchedEffect(Unit) { + setHideBottomNavigation?.invoke(true) + } + + // Restore bottom navigation when dismissed + androidx.compose.runtime.DisposableEffect(Unit) { + onDispose { + setHideBottomNavigation?.invoke(false) + } + } + + GalleryDetailScreen( + itemId = itemId, + onNavigateBack = onDismiss, + ) +} + +/** + * Data class to hold scroll state for snapshotFlow + */ +private data class GalleryScrollInfo( + val firstVisibleItemIndex: Int, + val firstVisibleItemScrollOffset: Int, + val isScrollInProgress: Boolean, +) diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryState.kt new file mode 100644 index 000000000..2c880d58a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryState.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import android.graphics.Bitmap +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class GalleryState( + val screenModal: Modal = Modal.None, + val mediaStoreInfo: MediaStoreInfo = MediaStoreInfo(), + val dropdownMenuShow: Boolean = false, + val selectionMode: Boolean = false, + val selection: List = emptyList(), + val grid: Grid = Grid.Fixed2, + val scrollToItemIndex: Int? = null, + // Immich-style: selected item shown as overlay, null = gallery grid visible + val selectedItemId: Long? = null, + val selectedItemIndex: Int? = null, + // Immich-style lazy loading: all IDs loaded at startup, bitmaps loaded on demand + val allIds: List = emptyList(), + val hiddenIds: Set = emptySet(), + val likedIds: Set = emptySet(), + val thumbnailCache: Map = emptyMap(), + val blurHashCache: Map = emptyMap(), // BlurHash strings for placeholders + val loadingIds: Set = emptySet(), // IDs currently being loaded + val isInitialLoading: Boolean = true, // True until IDs are loaded +) : MviState { + val totalItems: Int get() = allIds.size +} + +/** + * Lightweight item for grid display - bitmap loaded lazily + */ +@Immutable +data class GalleryGridItemUi( + val id: Long, + val bitmap: Bitmap?, // Nullable for lazy loading (Immich-style) + val hidden: Boolean, + val liked: Boolean = false, + val blurHash: String = "", // BlurHash for placeholder while loading +) { + // Cache ImageBitmap to avoid recreation on each recomposition + @Transient + private var _imageBitmap: ImageBitmap? = null + + val imageBitmap: ImageBitmap? + get() = bitmap?.let { bmp -> + _imageBitmap ?: bmp.asImageBitmap().also { _imageBitmap = it } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryViewModel.kt new file mode 100644 index 000000000..d1a4d0ebb --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryViewModel.kt @@ -0,0 +1,586 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import android.graphics.Bitmap +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.ThumbnailGenerator +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.BackgroundWorkResult +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllGalleryUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllUnlikedUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetAllGalleryUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsRawUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryPagedIdsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetMediaStoreInfoUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetThumbnailInfoUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.LikeItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.UnlikeItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.HideItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.UnhideItemsUseCase +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCase +import dev.minios.pdaiv1.presentation.core.GalleryItemStateEvent +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.utils.Constants +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.kotlin.subscribeBy +import kotlinx.coroutines.flow.Flow + +class GalleryViewModel( + dispatchersProvider: DispatchersProvider, + getMediaStoreInfoUseCase: GetMediaStoreInfoUseCase, + backgroundWorkObserver: BackgroundWorkObserver, + private val preferenceManager: PreferenceManager, + private val deleteAllGalleryUseCase: DeleteAllGalleryUseCase, + private val deleteAllUnlikedUseCase: DeleteAllUnlikedUseCase, + private val deleteGalleryItemsUseCase: DeleteGalleryItemsUseCase, + private val getGenerationResultPagedUseCase: GetGenerationResultPagedUseCase, + private val getGalleryPagedIdsUseCase: GetGalleryPagedIdsUseCase, + private val getGalleryItemsUseCase: GetGalleryItemsUseCase, + private val getGalleryItemsRawUseCase: GetGalleryItemsRawUseCase, + private val getThumbnailInfoUseCase: GetThumbnailInfoUseCase, + private val base64ToBitmapConverter: Base64ToBitmapConverter, + private val thumbnailGenerator: ThumbnailGenerator, + private val galleryExporter: GalleryExporter, + private val schedulersProvider: SchedulersProvider, + private val mainRouter: MainRouter, + private val drawerRouter: DrawerRouter, + private val mediaStoreGateway: MediaStoreGateway, + private val mediaFileManager: MediaFileManager, + private val getAllGalleryUseCase: GetAllGalleryUseCase, + private val galleryItemStateEvent: GalleryItemStateEvent, + private val likeItemsUseCase: LikeItemsUseCase, + private val unlikeItemsUseCase: UnlikeItemsUseCase, + private val hideItemsUseCase: HideItemsUseCase, + private val unhideItemsUseCase: UnhideItemsUseCase, +) : MviRxViewModel() { + + override val initialState = GalleryState() + + override val effectDispatcher = dispatchersProvider.immediate + + private val config = PagingConfig( + pageSize = Constants.PAGINATION_PAYLOAD_SIZE, + initialLoadSize = Constants.PAGINATION_PAYLOAD_SIZE * 3, + prefetchDistance = Constants.PAGINATION_PAYLOAD_SIZE * 3, + enablePlaceholders = false, + ) + + private val pager: Pager = Pager( + config = config, + initialKey = GalleryPagingSource.FIRST_KEY, + pagingSourceFactory = { + GalleryPagingSource( + getGenerationResultPagedUseCase, + thumbnailGenerator, + schedulersProvider, + ) + } + ) + + val pagingFlow: Flow> = pager.flow + + init { + // Immich-style: Load all IDs at startup for instant scrolling + loadAllIds() + + !preferenceManager + .observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { settings -> + updateState { it.copy(grid = settings.galleryGrid) } + } + + !getMediaStoreInfoUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { info -> + updateState { it.copy(mediaStoreInfo = info) } + } + + !backgroundWorkObserver + .observeResult() + .ofType(BackgroundWorkResult.Success::class.java) + .doOnNext { loadAllIds() } // Reload IDs when new image generated + .map { GalleryEffect.Refresh } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) + + // Listen for new images during batch generation + !backgroundWorkObserver + .observeNewImage() + .doOnNext { loadAllIds() } // Reload IDs when each new image is saved + .map { GalleryEffect.Refresh } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) + + // Listen for gallery changes (deletions from detail view) + !backgroundWorkObserver + .observeGalleryChanged() + .doOnNext { loadAllIds() } // Reload IDs when items deleted + .map { GalleryEffect.Refresh } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::emitEffect) + + // Subscribe to real-time hidden state changes from detail view + !galleryItemStateEvent.observeHiddenChanges() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { change -> + updateState { state -> + state.copy( + hiddenIds = if (change.hidden) state.hiddenIds + change.itemId else state.hiddenIds - change.itemId + ) + } + } + + // Subscribe to real-time liked state changes from detail view + !galleryItemStateEvent.observeLikedChanges() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { change -> + updateState { state -> + state.copy( + likedIds = if (change.liked) state.likedIds + change.itemId else state.likedIds - change.itemId + ) + } + } + } + + private fun loadAllIds() { + !getGalleryPagedIdsUseCase.withBlurHash() + .subscribeOn(schedulersProvider.io) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { idsWithBlurHash -> + val ids = idsWithBlurHash.map { it.first } + val blurHashMap = idsWithBlurHash + .filter { it.second.isNotEmpty() } + .associate { it.first to it.second } + updateState { state -> + state.copy( + allIds = ids, + blurHashCache = state.blurHashCache + blurHashMap, + isInitialLoading = false, + ) + } + } + } + + private fun loadThumbnails(ids: List) { + // Filter out already loaded or loading IDs + val idsToLoad = ids.filter { id -> + !currentState.thumbnailCache.containsKey(id) && + !currentState.loadingIds.contains(id) + } + + if (idsToLoad.isEmpty()) return + + // Mark as loading + updateState { state -> + state.copy(loadingIds = state.loadingIds + idsToLoad.toSet()) + } + + // Load thumbnails using raw data (without loading full images into memory) + !getGalleryItemsRawUseCase(idsToLoad) + .subscribeOn(schedulersProvider.io) + .flatMapObservable { Observable.fromIterable(it) } + .flatMap( + { ai -> + // Use file-based loading if mediaPath is available, otherwise fall back to base64 + val thumbnailSingle = if (ai.mediaPath.isNotEmpty()) { + // Convert relative path to absolute path + val absolutePath = mediaFileManager.getMediaFile(ai.mediaPath).absolutePath + thumbnailGenerator.generateFromFile( + id = ai.id.toString(), + filePath = absolutePath, + ) + } else if (ai.image.isNotEmpty()) { + thumbnailGenerator.generate( + id = ai.id.toString(), + base64ImageString = ai.image, + ) + } else { + // No image available, skip + io.reactivex.rxjava3.core.Single.never() + } + thumbnailSingle + .map { bitmap -> ThumbnailData(ai.id, ai.hidden, ai.liked, bitmap, ai.blurHash) } + .toObservable() + .subscribeOn(schedulersProvider.computation) + }, + MAX_CONCURRENT_THUMBNAIL_LOADS, + ) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { data: ThumbnailData -> + updateState { state -> + state.copy( + thumbnailCache = state.thumbnailCache + (data.id to data.bitmap), + blurHashCache = if (data.blurHash.isNotEmpty()) state.blurHashCache + (data.id to data.blurHash) else state.blurHashCache, + loadingIds = state.loadingIds - data.id, + hiddenIds = if (data.hidden) state.hiddenIds + data.id else state.hiddenIds - data.id, + likedIds = if (data.liked) state.likedIds + data.id else state.likedIds - data.id, + ) + } + } + } + + private data class ThumbnailData( + val id: Long, + val hidden: Boolean, + val liked: Boolean, + val bitmap: Bitmap, + val blurHash: String, + ) + + override fun processIntent(intent: GalleryIntent) { + when (intent) { + GalleryIntent.DismissDialog -> setActiveModal(Modal.None) + + GalleryIntent.Export.All.Request -> setActiveModal(Modal.ConfirmExport(true)) + + GalleryIntent.Export.All.Confirm -> launchGalleryExport(true) + + GalleryIntent.Export.Selection.Request -> setActiveModal(Modal.ConfirmExport(false)) + + GalleryIntent.Export.Selection.Confirm -> launchGalleryExport(false) + + is GalleryIntent.OpenItem -> { + updateState { + it.copy( + selectedItemId = intent.id, + selectedItemIndex = intent.index, + scrollToItemIndex = intent.index + ) + } + } + + GalleryIntent.CloseItem -> { + // Hidden/liked states are synced in real-time via GalleryItemStateEvent + updateState { it.copy(selectedItemId = null, selectedItemIndex = null) } + } + + GalleryIntent.ClearScrollPosition -> { + updateState { it.copy(scrollToItemIndex = null) } + } + + is GalleryIntent.OpenMediaStoreFolder -> emitEffect(GalleryEffect.OpenUri(intent.uri)) + + is GalleryIntent.Drawer -> when (intent.intent) { + DrawerIntent.Close -> drawerRouter.closeDrawer() + DrawerIntent.Open -> drawerRouter.openDrawer() + } + + is GalleryIntent.ChangeSelectionMode -> updateState { + it.copy( + selectionMode = intent.flag, + selection = if (!intent.flag) emptyList() else it.selection, + ) + } + + is GalleryIntent.ToggleItemSelection -> updateState { + val selectionIndex = it.selection.indexOf(intent.id) + val newSelection = it.selection.toMutableList() + if (selectionIndex != -1) { + newSelection.removeAt(selectionIndex) + } else { + newSelection.add(intent.id) + } + it.copy(selection = newSelection) + } + + GalleryIntent.Delete.Selection.Request -> setActiveModal( + Modal.DeleteImageConfirm(isAll = false, isMultiple = true) + ) + + GalleryIntent.Delete.Selection.Confirm -> !deleteGalleryItemsUseCase + .invoke(currentState.selection) + .processDeletion() + + GalleryIntent.Delete.All.Request -> setActiveModal( + Modal.DeleteImageConfirm(isAll = true, isMultiple = false) + ) + + GalleryIntent.Delete.All.Confirm -> !deleteAllGalleryUseCase() + .processDeletion() + + GalleryIntent.Delete.AllUnliked.Request -> setActiveModal( + Modal.DeleteUnlikedConfirm + ) + + GalleryIntent.Delete.AllUnliked.Confirm -> !deleteAllUnlikedUseCase() + .processDeletion() + + GalleryIntent.LikeSelection -> { + val selection = currentState.selection + if (selection.isNotEmpty()) { + // Toggle logic: if ALL selected are liked -> unlike, otherwise -> like all + val allLiked = selection.all { it in currentState.likedIds } + val useCase = if (allLiked) unlikeItemsUseCase(selection) else likeItemsUseCase(selection) + val newLikedState = !allLiked + + !useCase + .subscribeOn(schedulersProvider.io) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { + // Update liked state and emit events for real-time sync + updateState { state -> + val newLikedIds = if (newLikedState) { + state.likedIds + selection.toSet() + } else { + state.likedIds - selection.toSet() + } + state.copy( + likedIds = newLikedIds, + selectionMode = false, + selection = emptyList(), + ) + } + selection.forEach { id -> + galleryItemStateEvent.emitLikedChange(id, newLikedState) + } + } + } + } + + GalleryIntent.HideSelection -> { + val selection = currentState.selection + if (selection.isNotEmpty()) { + // Toggle logic: if ALL selected are hidden -> unhide, otherwise -> hide all + val allHidden = selection.all { it in currentState.hiddenIds } + val useCase = if (allHidden) unhideItemsUseCase(selection) else hideItemsUseCase(selection) + val newHiddenState = !allHidden + + !useCase + .subscribeOn(schedulersProvider.io) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { + // Update hidden state and emit events for real-time sync + updateState { state -> + val newHiddenIds = if (newHiddenState) { + state.hiddenIds + selection.toSet() + } else { + state.hiddenIds - selection.toSet() + } + state.copy( + hiddenIds = newHiddenIds, + selectionMode = false, + selection = emptyList(), + ) + } + selection.forEach { id -> + galleryItemStateEvent.emitHiddenChange(id, newHiddenState) + } + } + } + } + + GalleryIntent.UnselectAll -> updateState { + it.copy(selection = emptyList()) + } + + GalleryIntent.Dropdown.Toggle -> updateState { + it.copy(dropdownMenuShow = !it.dropdownMenuShow) + } + + GalleryIntent.Dropdown.Show -> updateState { + it.copy(dropdownMenuShow = true) + } + + GalleryIntent.Dropdown.Close -> updateState { + it.copy(dropdownMenuShow = false) + } + + GalleryIntent.SaveToGallery.All.Request -> setActiveModal( + Modal.ConfirmSaveToGallery(saveAll = true) + ) + + GalleryIntent.SaveToGallery.All.Confirm -> saveAllToGallery() + + GalleryIntent.SaveToGallery.Selection.Request -> setActiveModal( + Modal.ConfirmSaveToGallery(saveAll = false) + ) + + GalleryIntent.SaveToGallery.Selection.Confirm -> saveSelectionToGallery() + + GalleryIntent.GridZoom.ZoomIn -> { + val newGrid = dev.minios.pdaiv1.domain.entity.Grid.zoomIn(currentState.grid) + preferenceManager.galleryGrid = newGrid + } + + GalleryIntent.GridZoom.ZoomOut -> { + val newGrid = dev.minios.pdaiv1.domain.entity.Grid.zoomOut(currentState.grid) + preferenceManager.galleryGrid = newGrid + } + + is GalleryIntent.DragSelection.Start -> { + updateState { + it.copy( + selectionMode = true, + selection = listOf(intent.itemId), + ) + } + } + + is GalleryIntent.DragSelection.UpdateRange -> { + updateState { + it.copy(selection = intent.itemIds.distinct()) + } + } + + GalleryIntent.DragSelection.End -> { + // Drag ended - selection is already updated + } + + is GalleryIntent.LoadThumbnails -> loadThumbnails(intent.ids) + } + } + + private fun Completable.processDeletion() = this + .doOnSubscribe { setActiveModal(Modal.None) } + .doOnComplete { loadAllIds() } // Reload IDs after deletion + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { + updateState { + it.copy( + selectionMode = false, + selection = emptyList() + ) + } + emitEffect(GalleryEffect.Refresh) + } + + private fun launchGalleryExport(exportAll: Boolean) = !galleryExporter + .invoke(if (exportAll) null else currentState.selection) + .doOnSubscribe { setActiveModal(Modal.ExportInProgress) } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + setActiveModal( + Modal.Error( + (t.localizedMessage ?: "Something went wrong").asUiText() + ) + ) + errorLog(t) + }, + onSuccess = { zipFile -> + setActiveModal(Modal.None) + emitEffect(GalleryEffect.Share(zipFile)) + } + ) + + private fun setActiveModal(dialog: Modal) = updateState { + it.copy(screenModal = dialog, dropdownMenuShow = false) + } + + private fun saveAllToGallery() { + // Get all IDs first, then process in parallel + !getGalleryPagedIdsUseCase() + .doOnSubscribe { setActiveModal(Modal.ExportInProgress) } + .flatMapObservable { allIds -> + io.reactivex.rxjava3.core.Observable.fromIterable(allIds) + } + .flatMap({ id: Long -> + getGalleryItemsRawUseCase(listOf(id)) + .flatMapCompletable { items -> + val item = items.firstOrNull() + if (item == null) { + Completable.complete() + } else { + Completable.fromAction { exportItemToGallery(item) } + } + } + .toObservable() + }, 4) // Process 4 items in parallel + .subscribeOn(schedulersProvider.io) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + setActiveModal( + Modal.Error( + (t.localizedMessage ?: "Something went wrong").asUiText() + ) + ) + errorLog(t) + }, + onComplete = { + setActiveModal(Modal.None) + emitEffect(GalleryEffect.AllImagesSavedToGallery) + } + ) + } + + private fun saveSelectionToGallery() { + val selectedIds = currentState.selection + !getGalleryItemsRawUseCase(selectedIds) + .doOnSubscribe { setActiveModal(Modal.ExportInProgress) } + .flatMapObservable { io.reactivex.rxjava3.core.Observable.fromIterable(it) } + .flatMap({ item -> + Completable.fromAction { exportItemToGallery(item) } + .toObservable() + }, 4) // Process 4 items in parallel + .subscribeOn(schedulersProvider.io) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + setActiveModal( + Modal.Error( + (t.localizedMessage ?: "Something went wrong").asUiText() + ) + ) + errorLog(t) + }, + onComplete = { + setActiveModal(Modal.None) + updateState { + it.copy( + selectionMode = false, + selection = emptyList() + ) + } + emitEffect(GalleryEffect.SelectionImagesSavedToGallery) + } + ) + } + + private fun exportItemToGallery(item: dev.minios.pdaiv1.domain.entity.AiGenerationResult) { + // Export directly from file if available (fast path - no memory allocation for image data) + if (item.mediaPath.isNotEmpty() && mediaFileManager.isFilePath(item.mediaPath)) { + val file = mediaFileManager.getMediaFile(item.mediaPath) + if (file.exists()) { + mediaStoreGateway.exportFromFile( + fileName = "pdai_${item.id}_${System.currentTimeMillis()}", + sourceFile = file, + ) + return + } + } + // Fallback to base64 if no file path (legacy data) + if (item.image.isNotEmpty()) { + val output = base64ToBitmapConverter(Base64ToBitmapConverter.Input(item.image)).blockingGet() + val stream = java.io.ByteArrayOutputStream() + output.bitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, stream) + mediaStoreGateway.exportToFile( + fileName = "pdai_${item.id}_${System.currentTimeMillis()}", + content = stream.toByteArray(), + ) + } + } + + companion object { + private const val MAX_CONCURRENT_THUMBNAIL_LOADS = 32 + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/selection/DragSelectionState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/selection/DragSelectionState.kt new file mode 100644 index 000000000..6a91afc2d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/selection/DragSelectionState.kt @@ -0,0 +1,57 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list.selection + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset + +/** + * State holder for drag selection in gallery grid. + * Tracks the start and current positions of drag gesture. + */ +@Stable +class DragSelectionState { + var isActive by mutableStateOf(false) + private set + + var startPosition by mutableStateOf(Offset.Zero) + private set + + var currentPosition by mutableStateOf(Offset.Zero) + private set + + var anchorIndex by mutableStateOf(null) + private set + + fun startDrag(position: Offset, index: Int) { + isActive = true + startPosition = position + currentPosition = position + anchorIndex = index + } + + fun updateDrag(position: Offset) { + if (isActive) { + currentPosition = position + } + } + + fun endDrag() { + isActive = false + startPosition = Offset.Zero + currentPosition = Offset.Zero + anchorIndex = null + } + + fun reset() { + endDrag() + } +} + +@Composable +fun rememberDragSelectionState(): DragSelectionState { + return remember { DragSelectionState() } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/selection/DragSelectionUtils.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/selection/DragSelectionUtils.kt new file mode 100644 index 000000000..9ddbe6fba --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/gallery/list/selection/DragSelectionUtils.kt @@ -0,0 +1,76 @@ +package dev.minios.pdaiv1.presentation.screen.gallery.list.selection + +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.ui.geometry.Offset +import kotlin.math.max +import kotlin.math.min + +/** + * Utility functions for drag selection in gallery grid. + */ +object DragSelectionUtils { + + /** + * Calculates the range of indices between anchor and current position. + * Returns indices in the range [minIndex, maxIndex]. + */ + fun calculateSelectedRange( + anchorIndex: Int, + currentIndex: Int, + ): IntRange { + val minIndex = min(anchorIndex, currentIndex) + val maxIndex = max(anchorIndex, currentIndex) + return minIndex..maxIndex + } + + /** + * Estimates the item index at a given position in the grid. + * This is an approximation based on grid layout. + * + * @param position The position in pixels + * @param gridState The lazy grid state + * @param columns Number of columns in the grid + * @param itemSize Approximate size of each item (including spacing) + * @param contentPadding Content padding of the grid + * @return Estimated item index, or null if outside grid bounds + */ + fun estimateItemIndexAtPosition( + position: Offset, + gridState: LazyGridState, + columns: Int, + itemSize: Float, + contentPadding: Float, + ): Int? { + if (position.x < contentPadding || position.y < contentPadding) { + return null + } + + val adjustedX = position.x - contentPadding + val adjustedY = position.y - contentPadding + + val column = (adjustedX / itemSize).toInt().coerceIn(0, columns - 1) + val row = (adjustedY / itemSize).toInt() + + val scrollOffset = gridState.firstVisibleItemScrollOffset + val firstVisibleRow = gridState.firstVisibleItemIndex / columns + val actualRow = firstVisibleRow + row + (scrollOffset / itemSize).toInt() + + return actualRow * columns + column + } + + /** + * Determines if auto-scroll should be triggered based on drag position. + * Returns scroll direction: negative for up, positive for down, zero for no scroll. + */ + fun getAutoScrollDirection( + dragPosition: Offset, + viewportHeight: Float, + edgeThreshold: Float = 100f, + ): Float { + return when { + dragPosition.y < edgeThreshold -> -1f + dragPosition.y > viewportHeight - edgeThreshold -> 1f + else -> 0f + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationIntent.kt new file mode 100644 index 000000000..c4f72fda6 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationIntent.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.presentation.screen.home + +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface HomeNavigationIntent : MviIntent { + + data class Route(val navRoute: NavigationRoute) : HomeNavigationIntent + + data class Update(val navRoute: NavigationRoute) : HomeNavigationIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationMappers.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationMappers.kt new file mode 100644 index 000000000..b57836fbd --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationMappers.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.presentation.screen.home + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute + +fun AiGenerationResult.Type.mapToRoute(): NavigationRoute = when (this) { + AiGenerationResult.Type.TEXT_TO_IMAGE -> NavigationRoute.HomeNavigation.TxtToImg + AiGenerationResult.Type.IMAGE_TO_IMAGE -> NavigationRoute.HomeNavigation.ImgToImg +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationScreen.kt new file mode 100644 index 000000000..daa7e1376 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationScreen.kt @@ -0,0 +1,135 @@ +package dev.minios.pdaiv1.presentation.screen.home + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.presentation.model.NavItem +import dev.minios.pdaiv1.presentation.navigation.LocalHideBottomNavigation +import dev.minios.pdaiv1.presentation.navigation.LocalSetHideBottomNavigation +import dev.minios.pdaiv1.presentation.widget.connectivity.ConnectivityComposable +import dev.minios.pdaiv1.presentation.widget.item.NavigationItemIcon +import kotlinx.coroutines.launch + +@Composable +fun HomeNavigationScreen( + navItems: List = emptyList(), +) { + require(navItems.isNotEmpty()) { "navItems collection must not be empty." } + + val scope = rememberCoroutineScope() + val pagerState = rememberPagerState( + initialPage = 0, + pageCount = { navItems.size } + ) + + // State for controlling bottom navigation visibility (used by gallery detail overlay) + var hideBottomNavigation by remember { mutableStateOf(false) } + + // Animate bottom navigation offset for smooth hide/show without layout changes + val bottomNavOffset by animateFloatAsState( + targetValue = if (hideBottomNavigation) 1f else 0f, + animationSpec = tween(durationMillis = 200), + label = "bottom_nav_offset" + ) + + // Bottom navigation bar height (approximate) + val bottomNavHeight = 80.dp + val density = LocalDensity.current + val bottomNavHeightPx = with(density) { bottomNavHeight.toPx() } + + CompositionLocalProvider( + LocalHideBottomNavigation provides hideBottomNavigation, + LocalSetHideBottomNavigation provides { hide -> hideBottomNavigation = hide }, + ) { + Scaffold( + modifier = Modifier.imePadding(), + bottomBar = { + // Use graphicsLayer for smooth hide/show without layout changes + NavigationBar( + modifier = Modifier + .graphicsLayer { + translationY = bottomNavHeightPx * bottomNavOffset + alpha = 1f - bottomNavOffset + }, + containerColor = MaterialTheme.colorScheme.surface, + ) { + navItems.forEachIndexed { index, item -> + val selected = pagerState.currentPage == index + NavigationBarItem( + selected = selected, + label = { + Text( + text = item.name.asString(), + color = LocalContentColor.current, + ) + }, + colors = NavigationBarItemDefaults.colors().copy( + selectedIndicatorColor = MaterialTheme.colorScheme.primary, + ), + icon = { NavigationItemIcon(item.icon) }, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + }, + ) + } + } + }, + content = { _ -> + // All screens use CollapsibleScaffold which manages its own layout + // Bottom padding is handled by each screen individually + Box(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.fillMaxSize()) { + // Hide connectivity widget in fullscreen mode + AnimatedVisibility(visible = !hideBottomNavigation) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surface), + contentAlignment = Alignment.Center, + ) { + ConnectivityComposable() + } + } + HorizontalPager( + state = pagerState, + modifier = Modifier.fillMaxSize(), + userScrollEnabled = !hideBottomNavigation, // Disable swipe in fullscreen mode + ) { page -> + navItems[page].content?.invoke() + } + } + } + } + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationViewModel.kt new file mode 100644 index 000000000..cd9200643 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/home/HomeNavigationViewModel.kt @@ -0,0 +1,67 @@ +package dev.minios.pdaiv1.presentation.screen.home + +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.navigation.router.home.HomeRouter +import com.shifthackz.android.core.mvi.EmptyState +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.kotlin.subscribeBy +import java.util.concurrent.TimeUnit + +class HomeNavigationViewModel( + generationFormUpdateEvent: GenerationFormUpdateEvent, + preferenceManager: PreferenceManager, + dispatchersProvider: DispatchersProvider, + schedulersProvider: SchedulersProvider, + private val homeRouter: HomeRouter, +) : MviRxViewModel() { + + override val initialState = EmptyState + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !homeRouter + .observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda) { effect -> + (effect as? NavigationEffect.Home.Route)?.let(::emitEffect) + } + + !generationFormUpdateEvent + .observeRoute() + .map(AiGenerationResult.Type::mapToRoute) + .map(HomeNavigationIntent::Route) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::processIntent) + + !generationFormUpdateEvent + .observeFalAiRoute() + .map { NavigationRoute.HomeNavigation.FalAi } + .map(HomeNavigationIntent::Route) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::processIntent) + + !Observable + .timer(250L, TimeUnit.MILLISECONDS) + .flatMapCompletable { preferenceManager.refresh() } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) + } + + override fun processIntent(intent: HomeNavigationIntent) { + when (intent) { + is HomeNavigationIntent.Route -> homeRouter.navigateToRoute(intent.navRoute) + is HomeNavigationIntent.Update -> homeRouter.updateExternallyWithoutNavigation(intent.navRoute) + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageEffect.kt new file mode 100644 index 000000000..130b98583 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageEffect.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.presentation.screen.img2img + +import com.shifthackz.android.core.mvi.MviEffect + +enum class ImageToImageEffect : MviEffect { + GalleryPicker, CameraPicker; +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageScreen.kt similarity index 85% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageScreen.kt index a069c396b..62b3f5c91 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageScreen.kt @@ -1,11 +1,13 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.screen.img2img +package dev.minios.pdaiv1.presentation.screen.img2img import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -17,6 +19,7 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -41,7 +44,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -50,6 +52,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource @@ -57,32 +61,32 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.FileProvider -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.math.roundTo -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.core.ImageToImageIntent -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.screen.inpaint.components.InPaintComponent -import com.shifthackz.aisdv1.presentation.theme.sliderColors -import com.shifthackz.aisdv1.presentation.utils.Constants.DENOISING_STRENGTH_MAX -import com.shifthackz.aisdv1.presentation.utils.Constants.DENOISING_STRENGTH_MIN -import com.shifthackz.aisdv1.presentation.utils.PermissionUtil -import com.shifthackz.aisdv1.presentation.utils.uriToBitmap -import com.shifthackz.aisdv1.presentation.widget.input.GenerationInputForm -import com.shifthackz.aisdv1.presentation.widget.toolbar.GenerationBottomToolbar -import com.shifthackz.aisdv1.presentation.widget.work.BackgroundWorkWidget +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.core.ImageToImageIntent +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.presentation.utils.Constants.DENOISING_STRENGTH_MAX +import dev.minios.pdaiv1.presentation.utils.Constants.DENOISING_STRENGTH_MIN +import dev.minios.pdaiv1.presentation.utils.PermissionUtil +import dev.minios.pdaiv1.presentation.utils.uriToBitmap +import dev.minios.pdaiv1.presentation.widget.input.GenerationInputForm +import dev.minios.pdaiv1.presentation.widget.scaffold.CollapsibleScaffold +import dev.minios.pdaiv1.presentation.widget.toolbar.GenerationBottomToolbar +import dev.minios.pdaiv1.presentation.widget.work.BackgroundWorkWidget import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject import java.io.File -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ImageToImageScreen() { @@ -161,19 +165,19 @@ private fun ScreenContent( val negativePromptChipTextFieldState = remember { mutableStateOf(TextFieldValue()) } val keyboardController = LocalSoftwareKeyboardController.current Box(modifier) { - Scaffold( - topBar = { - Column { - CenterAlignedTopAppBar( - navigationIcon = { - IconButton(onClick = { - processIntent(GenerationMviIntent.Drawer(DrawerIntent.Open)) - }) { - Icon( - imageVector = Icons.Default.Menu, - contentDescription = "Menu", - ) - } + CollapsibleScaffold( + bottomToolbarHeight = 150.dp, + topBarContent = { + CenterAlignedTopAppBar( + navigationIcon = { + IconButton(onClick = { + processIntent(GenerationMviIntent.Drawer(DrawerIntent.Open)) + }) { + Icon( + imageVector = Icons.Default.Menu, + contentDescription = "Menu", + ) + } }, title = { Text( @@ -203,26 +207,16 @@ private fun ScreenContent( }, windowInsets = WindowInsets(0, 0, 0, 0), ) - BackgroundWorkWidget( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .padding(vertical = 4.dp), - ) - } }, - content = { paddingValues -> + contentScrollable = { when (state.mode) { ServerSource.AUTOMATIC1111, ServerSource.SWARM_UI, ServerSource.HORDE, ServerSource.STABILITY_AI, - ServerSource.HUGGING_FACE -> { - val scrollState = rememberScrollState() - Column( - modifier = Modifier - .padding(paddingValues) - .verticalScroll(scrollState) - ) { + ServerSource.HUGGING_FACE, + ServerSource.LOCAL_QUALCOMM_QNN -> { + Column { InputImageState( modifier = Modifier.fillMaxWidth(), state = state, @@ -263,7 +257,6 @@ private fun ScreenContent( else -> { Column( modifier = Modifier - .padding(paddingValues) .padding(horizontal = 36.dp) .fillMaxSize(), verticalArrangement = Arrangement.Center, @@ -409,18 +402,45 @@ private fun InputImageState( is ImageToImageState.ImageState.Image -> Column( modifier = modifier, ) { - InPaintComponent( - drawMode = false, - bitmap = state.imageState.bitmap, - inPaint = state.inPaintModel, - ) + Box( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 300.dp) + .padding(horizontal = 16.dp) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outline, + shape = RoundedCornerShape(8.dp) + ) + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant), + contentAlignment = Alignment.Center, + ) { + state.imageState.bitmap?.asImageBitmap()?.let { imageBitmap -> + Image( + modifier = Modifier.fillMaxSize(), + bitmap = imageBitmap, + contentDescription = null, + contentScale = ContentScale.Fit, + ) + } + state.inPaintModel.bitmap?.asImageBitmap()?.let { maskBitmap -> + Image( + modifier = Modifier.fillMaxSize(), + bitmap = maskBitmap, + contentDescription = null, + contentScale = ContentScale.Fit, + alpha = 0.5f, + ) + } + } Row( modifier = Modifier .padding(top = 8.dp) .padding(horizontal = 16.dp) .fillMaxSize(), ) { - if (state.mode == ServerSource.AUTOMATIC1111) { + if (state.mode == ServerSource.AUTOMATIC1111 || state.mode == ServerSource.LOCAL_QUALCOMM_QNN) { OutlinedButton( modifier = Modifier.weight(1f), onClick = { processIntent(ImageToImageIntent.InPaint) }, diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageState.kt new file mode 100644 index 000000000..663be1574 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageState.kt @@ -0,0 +1,214 @@ +package dev.minios.pdaiv1.presentation.screen.img2img + +import android.graphics.Bitmap +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.domain.entity.ADetailerConfig +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.domain.entity.HiresConfig +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.QnnHiresConfig +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.OpenAiQuality +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.OpenAiStyle +import dev.minios.pdaiv1.domain.entity.Scheduler +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.StabilityAiClipGuidance +import dev.minios.pdaiv1.domain.entity.StabilityAiStylePreset +import dev.minios.pdaiv1.presentation.core.GenerationMviState +import dev.minios.pdaiv1.presentation.model.FalAiEndpointUi +import dev.minios.pdaiv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.model.Modal + +@Immutable +data class ImageToImageState( + val imageState: ImageState = ImageState.None, + val imageBase64: String = "", + val denoisingStrength: Float = 0.75f, + val inPaintModel: InPaintModel = InPaintModel(), + override val onBoardingDemo: Boolean = false, + override val screenModal: Modal = Modal.None, + override val mode: ServerSource = ServerSource.AUTOMATIC1111, + override val modelType: ModelType = ModelType.SD_1_5, + override val advancedToggleButtonVisible: Boolean = true, + override val advancedOptionsVisible: Boolean = false, + override val formPromptTaggedInput: Boolean = false, + override val prompt: String = "", + override val negativePrompt: String = "", + override val width: String = 512.toString(), + override val height: String = 512.toString(), + override val samplingSteps: Int = 20, + override val cfgScale: Float = 7f, + override val distilledCfgScale: Float = 3.5f, + override val restoreFaces: Boolean = false, + override val seed: String = "", + override val subSeed: String = "", + override val subSeedStrength: Float = 0f, + override val selectedSampler: String = "", + override val availableSamplers: List = emptyList(), + override val selectedScheduler: Scheduler = Scheduler.AUTOMATIC, + override val availableForgeModules: List = emptyList(), + override val selectedForgeModules: List = emptyList(), + override val aDetailerConfig: ADetailerConfig = ADetailerConfig.DISABLED, + override val hiresConfig: HiresConfig = HiresConfig.DISABLED, + override val selectedStylePreset: StabilityAiStylePreset = StabilityAiStylePreset.NONE, + override val selectedClipGuidancePreset: StabilityAiClipGuidance = StabilityAiClipGuidance.NONE, + override val openAiModel: OpenAiModel = OpenAiModel.DALL_E_2, + override val openAiSize: OpenAiSize = OpenAiSize.W1024_H1024, + override val openAiQuality: OpenAiQuality = OpenAiQuality.STANDARD, + override val openAiStyle: OpenAiStyle = OpenAiStyle.VIVID, + override val widthValidationError: UiText? = null, + override val heightValidationError: UiText? = null, + override val nsfw: Boolean = false, + override val batchCount: Int = 1, + override val generateButtonEnabled: Boolean = true, + override val falAiEndpoints: List = emptyList(), + override val falAiSelectedEndpoint: FalAiEndpointUi? = null, + override val falAiPropertyValues: Map = emptyMap(), + override val falAiAdvancedVisible: Boolean = false, + override val qnnRunOnCpu: Boolean = false, + override val qnnHiresConfig: QnnHiresConfig = QnnHiresConfig.DISABLED, + override val modelName: String = "", +) : GenerationMviState() { + + sealed interface ImageState { + + val isEmpty: Boolean + get() = this is None + + data object None : ImageState + data class Image(val bitmap: Bitmap) : ImageState + } + + override fun copyState( + onBoardingDemo: Boolean, + screenModal: Modal, + mode: ServerSource, + modelType: ModelType, + advancedToggleButtonVisible: Boolean, + advancedOptionsVisible: Boolean, + formPromptTaggedInput: Boolean, + prompt: String, + negativePrompt: String, + width: String, + height: String, + samplingSteps: Int, + cfgScale: Float, + distilledCfgScale: Float, + restoreFaces: Boolean, + seed: String, + subSeed: String, + subSeedStrength: Float, + selectedSampler: String, + availableSamplers: List, + selectedScheduler: Scheduler, + availableForgeModules: List, + selectedForgeModules: List, + aDetailerConfig: ADetailerConfig, + hiresConfig: HiresConfig, + selectedStylePreset: StabilityAiStylePreset, + selectedClipGuidancePreset: StabilityAiClipGuidance, + openAiModel: OpenAiModel, + openAiSize: OpenAiSize, + openAiQuality: OpenAiQuality, + openAiStyle: OpenAiStyle, + widthValidationError: UiText?, + heightValidationError: UiText?, + nsfw: Boolean, + batchCount: Int, + generateButtonEnabled: Boolean, + falAiEndpoints: List, + falAiSelectedEndpoint: FalAiEndpointUi?, + falAiPropertyValues: Map, + falAiAdvancedVisible: Boolean, + qnnRunOnCpu: Boolean, + qnnHiresConfig: QnnHiresConfig, + modelName: String, + ): GenerationMviState = copy( + onBoardingDemo = onBoardingDemo, + screenModal = screenModal, + mode = mode, + modelType = modelType, + advancedToggleButtonVisible = advancedToggleButtonVisible, + advancedOptionsVisible = advancedOptionsVisible, + formPromptTaggedInput = formPromptTaggedInput, + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + samplingSteps = samplingSteps, + cfgScale = cfgScale, + distilledCfgScale = distilledCfgScale, + restoreFaces = restoreFaces, + seed = seed, + subSeed = subSeed, + subSeedStrength = subSeedStrength, + selectedSampler = selectedSampler, + availableSamplers = availableSamplers, + selectedScheduler = selectedScheduler, + availableForgeModules = availableForgeModules, + selectedForgeModules = selectedForgeModules, + aDetailerConfig = aDetailerConfig, + hiresConfig = hiresConfig, + selectedStylePreset = selectedStylePreset, + selectedClipGuidancePreset = selectedClipGuidancePreset, + openAiModel = openAiModel, + openAiSize = openAiSize, + openAiQuality = openAiQuality, + openAiStyle = openAiStyle, + widthValidationError = widthValidationError, + heightValidationError = heightValidationError, + nsfw = nsfw, + batchCount = batchCount, + generateButtonEnabled = generateButtonEnabled, + falAiEndpoints = falAiEndpoints, + falAiSelectedEndpoint = falAiSelectedEndpoint, + falAiPropertyValues = falAiPropertyValues, + falAiAdvancedVisible = falAiAdvancedVisible, + qnnRunOnCpu = qnnRunOnCpu, + qnnHiresConfig = qnnHiresConfig, + modelName = modelName, + ) + + fun preProcessed(pair: Pair): ImageToImageState = copy( + imageBase64 = pair.first, + inPaintModel = this.inPaintModel.copy(base64 = pair.second), + ) +} + +enum class ImagePickButton { + PHOTO, CAMERA +} + +fun ImageToImageState.mapToPayload(): ImageToImagePayload = with(this) { + ImageToImagePayload( + base64Image = imageBase64, + base64MaskImage = inPaintModel.base64, + denoisingStrength = denoisingStrength, + prompt = prompt.trim(), + negativePrompt = negativePrompt.trim(), + samplingSteps = samplingSteps, + cfgScale = cfgScale, + width = width.toIntOrNull() ?: 64, + height = height.toIntOrNull() ?: 64, + restoreFaces = restoreFaces, + seed = seed.trim(), + subSeed = subSeed.trim(), + subSeedStrength = subSeedStrength, + sampler = selectedSampler, + scheduler = selectedScheduler, + nsfw = if (mode == ServerSource.HORDE) nsfw else false, + batchCount = batchCount, + inPaintingMaskInvert = inPaintModel.maskMode.inverse, + inPaintFullResPadding = inPaintModel.onlyMaskedPaddingPx, + inPaintingFill = inPaintModel.maskContent.fill, + inPaintFullRes = inPaintModel.inPaintArea.fullRes, + maskBlur = inPaintModel.maskBlur, + stabilityAiClipGuidance = selectedClipGuidancePreset.takeIf { mode == ServerSource.STABILITY_AI }, + stabilityAiStylePreset = selectedStylePreset.takeIf { mode == ServerSource.STABILITY_AI }, + aDetailer = aDetailerConfig.takeIf { mode == ServerSource.AUTOMATIC1111 } ?: ADetailerConfig.DISABLED, + modelName = modelName, + ) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageViewModel.kt new file mode 100644 index 000000000..e65992b1f --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageViewModel.kt @@ -0,0 +1,290 @@ +package dev.minios.pdaiv1.presentation.screen.img2img + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.ImageToImagePayload +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActor +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.caching.SaveLastResultToCacheUseCase +import dev.minios.pdaiv1.domain.usecase.forgemodule.GetForgeModulesUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetRandomImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ImageToImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.SaveGenerationResultUseCase +import dev.minios.pdaiv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.core.GenerationMviViewModel +import dev.minios.pdaiv1.presentation.core.ImageToImageIntent +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintStateProducer +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.kotlin.subscribeBy +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +class ImageToImageViewModel( + dispatchersProvider: DispatchersProvider, + generationFormUpdateEvent: GenerationFormUpdateEvent, + getStableDiffusionSamplersUseCase: GetStableDiffusionSamplersUseCase, + getForgeModulesUseCase: GetForgeModulesUseCase, + observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + saveLastResultToCacheUseCase: SaveLastResultToCacheUseCase, + saveGenerationResultUseCase: SaveGenerationResultUseCase, + interruptGenerationUseCase: InterruptGenerationUseCase, + drawerRouter: DrawerRouter, + dimensionValidator: DimensionValidator, + private val imageToImageUseCase: ImageToImageUseCase, + private val getRandomImageUseCase: GetRandomImageUseCase, + private val bitmapToBase64Converter: BitmapToBase64Converter, + private val base64ToBitmapConverter: Base64ToBitmapConverter, + private val preferenceManager: PreferenceManager, + private val schedulersProvider: SchedulersProvider, + private val notificationManager: PushNotificationManager, + private val wakeLockInterActor: WakeLockInterActor, + private val inPaintStateProducer: InPaintStateProducer, + private val mainRouter: MainRouter, + private val backgroundTaskManager: BackgroundTaskManager, + private val backgroundWorkObserver: BackgroundWorkObserver, + private val buildInfoProvider: BuildInfoProvider, +) : GenerationMviViewModel( + preferenceManager = preferenceManager, + getStableDiffusionSamplersUseCase = getStableDiffusionSamplersUseCase, + getForgeModulesUseCase = getForgeModulesUseCase, + observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + saveLastResultToCacheUseCase = saveLastResultToCacheUseCase, + saveGenerationResultUseCase = saveGenerationResultUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, + mainRouter = mainRouter, + drawerRouter = drawerRouter, + dimensionValidator = dimensionValidator, + schedulersProvider = schedulersProvider, + backgroundWorkObserver = backgroundWorkObserver, +) { + + override val initialState = ImageToImageState() + + override val effectDispatcher = dispatchersProvider.immediate + + private val progressModal: Modal + get() = when (currentState.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX, + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> { + Modal.Generating(canCancel = preferenceManager.localOnnxAllowCancel) + } + ServerSource.LOCAL_QUALCOMM_QNN -> { + Modal.Generating(canCancel = true) + } + else -> Modal.Communicating() + } + + init { + !generationFormUpdateEvent + .observeImg2ImgForm() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onNext = { payload -> + (payload as? GenerationFormUpdateEvent.Payload.I2IForm) + ?.let(::updateFormPreviousAiGeneration) + ?.also { generationFormUpdateEvent.clear() } + }, + ) + + !inPaintStateProducer + .observeInPaint() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { inPaint -> + updateState { it.copy(inPaintModel = inPaint) } + } + } + + override fun processIntent(intent: GenerationMviIntent) { + when (intent) { + ImageToImageIntent.ClearImageInput -> updateState { + inPaintStateProducer.updateInPaint(it.inPaintModel.clear()) + it.copy(imageState = ImageToImageState.ImageState.None) + } + + ImageToImageIntent.FetchRandomPhoto -> fetchRandomImage { + getRandomImageUseCase() + .doOnSubscribe { setActiveModal(Modal.LoadingRandomImage) } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + setActiveModal( + Modal.Error( + UiText.Static( + t.localizedMessage ?: "Error" + ) + ) + ) + errorLog(t) + }, + onSuccess = { bitmap -> + setActiveModal(Modal.None) + updateState { state -> + inPaintStateProducer.updateInPaint(state.inPaintModel.clear()) + state.copy(imageState = ImageToImageState.ImageState.Image(bitmap)) + } + }, + ) + } + + is ImageToImageIntent.UpdateDenoisingStrength -> updateState { + it.copy(denoisingStrength = intent.value) + } + + ImageToImageIntent.Pick.Camera -> emitEffect(ImageToImageEffect.CameraPicker) + + ImageToImageIntent.Pick.Gallery -> emitEffect(ImageToImageEffect.GalleryPicker) + + is ImageToImageIntent.CropImage -> updateState { + it.copy(screenModal = Modal.Image.Crop(intent.bitmap)) + } + + is ImageToImageIntent.UpdateImage -> updateState { + it.copy( + screenModal = Modal.None, + imageState = ImageToImageState.ImageState.Image(intent.bitmap), + ) + } + + ImageToImageIntent.InPaint -> (currentState.imageState as? ImageToImageState.ImageState.Image) + ?.let { image -> inPaintStateProducer.updateBitmap(image.bitmap) } + ?.also { inPaintStateProducer.updateInPaint(currentState.inPaintModel) } + ?.also { mainRouter.navigateToInPaint() } + + else -> super.processIntent(intent) + } + } + + override fun generateDisposable() = when (currentState.imageState) { + is ImageToImageState.ImageState.Image -> generateImageToImagePayload() + .doOnSubscribe { + wakeLockInterActor.acquireWakelockUseCase() + setActiveModal(progressModal) + } + .flatMap(imageToImageUseCase::invoke) + .doFinally { wakeLockInterActor.releaseWakeLockUseCase() } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + notificationManager.createAndShowInstant( + LocalizationR.string.notification_fail_title.asUiText(), + LocalizationR.string.notification_fail_sub_title.asUiText(), + ) + setActiveModal( + Modal.Error( + UiText.Static( + t.localizedMessage ?: "Error" + ) + ) + ) + errorLog(t) + }, + onSuccess = { ai -> + notificationManager.createAndShowInstant( + LocalizationR.string.notification_finish_title.asUiText(), + LocalizationR.string.notification_finish_sub_title.asUiText(), + ) + setActiveModal( + Modal.Image.create( + list = ai, + autoSaveEnabled = preferenceManager.autoSaveAiResults, + reportEnabled = buildInfoProvider.type == BuildType.PLAY, + ) + ) + } + ) + + else -> Disposable.empty() + } + + override fun generateBackground() { + if (currentState.imageState !is ImageToImageState.ImageState.Image) return + !generateImageToImagePayload() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { payload -> + backgroundTaskManager.scheduleImageToImageTask(payload) + } + } + + override fun onReceivedHordeStatus(status: HordeProcessStatus) { + (currentState.screenModal as? Modal.Communicating) + ?.copy(hordeProcessStatus = status) + ?.let(::setActiveModal) + } + + override fun onReceivedLocalDiffusionStatus(status: LocalDiffusionStatus) { + (currentState.screenModal as? Modal.Generating) + ?.copy(status = status) + ?.let(::setActiveModal) + } + + override fun updateFormPreviousAiGeneration(payload: GenerationFormUpdateEvent.Payload) { + if (payload !is GenerationFormUpdateEvent.Payload.I2IForm) return + val base64 = if (payload.inputImage) payload.ai.inputImage else payload.ai.image + !base64ToBitmapConverter(Base64ToBitmapConverter.Input(base64)) + .map(Base64ToBitmapConverter.Output::bitmap) + .map(ImageToImageState.ImageState::Image) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onSuccess = { imageState -> + updateState { state -> + state.copy( + imageState = imageState, + inPaintModel = state.inPaintModel.clear(), + ) + } + } + ) + + return super.updateFormPreviousAiGeneration(payload) + } + + private fun generateImageToImagePayload(): Single = Single + .just( + Pair( + (currentState.imageState as ImageToImageState.ImageState.Image).bitmap, + currentState.inPaintModel.bitmap, + ) + ) + .flatMap { (bmp, maskBmp) -> + bitmapToBase64Converter(BitmapToBase64Converter.Input(bmp)) + .map(BitmapToBase64Converter.Output::base64ImageString) + .flatMap { base64 -> + maskBmp?.let { + bitmapToBase64Converter(BitmapToBase64Converter.Input(maskBmp)) + .map(BitmapToBase64Converter.Output::base64ImageString) + .map { maskBase64 -> base64 to maskBase64 } + } ?: run { + Single.just(base64 to "") + } + } + } + .map(currentState::preProcessed) + .map(ImageToImageState::mapToPayload) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintIntent.kt new file mode 100644 index 000000000..6aeea1a7c --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintIntent.kt @@ -0,0 +1,48 @@ +package dev.minios.pdaiv1.presentation.screen.inpaint + +import android.graphics.Bitmap +import androidx.compose.ui.graphics.Path +import dev.minios.pdaiv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface InPaintIntent : MviIntent { + + data object NavigateBack : InPaintIntent + + data class SelectTab(val tab: InPaintState.Tab) : InPaintIntent + + data class ChangeCapSize(val size: Int) : InPaintIntent + + data class DrawPath(val path: Path) : InPaintIntent + + data class DrawPathBmp(val bitmap: Bitmap?) : InPaintIntent + + enum class Action : InPaintIntent { + Undo, Clear, ToggleDrawing, ResetZoom; + } + + data class UpdateZoomScale(val scale: Float) : InPaintIntent + + data class UpdateZoomOffset(val offsetX: Float, val offsetY: Float) : InPaintIntent + + sealed interface Update : InPaintIntent { + + data class MaskBlur(val value: Int) : Update + + data class OnlyMaskedPadding(val value: Int) : Update + + data class MaskMode(val value: InPaintModel.MaskMode) : Update + + data class MaskContent(val value: InPaintModel.MaskContent) : Update + + data class Area(val value: InPaintModel.Area) : Update + } + + sealed interface ScreenModal : InPaintIntent { + + data class Show(val modal: Modal) : ScreenModal + + data object Dismiss : ScreenModal + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintScreen.kt similarity index 95% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintScreen.kt index 8c3050ed3..f5dc10177 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) -package com.shifthackz.aisdv1.presentation.screen.inpaint +package dev.minios.pdaiv1.presentation.screen.inpaint import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility @@ -46,13 +46,13 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.inpaint.components.CapSizeSlider -import com.shifthackz.aisdv1.presentation.screen.inpaint.forms.ImageDrawForm -import com.shifthackz.aisdv1.presentation.screen.inpaint.forms.InPaintParamsForm +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.inpaint.components.CapSizeSlider +import dev.minios.pdaiv1.presentation.screen.inpaint.forms.ImageDrawForm +import dev.minios.pdaiv1.presentation.screen.inpaint.forms.InPaintParamsForm import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun InPaintScreen( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintState.kt new file mode 100644 index 000000000..789600ab4 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintState.kt @@ -0,0 +1,42 @@ +package dev.minios.pdaiv1.presentation.screen.inpaint + +import android.graphics.Bitmap +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviState +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.R as PresentationR + +@Immutable +data class InPaintState( + val screenModal: Modal = Modal.None, + val bitmap: Bitmap? = null, + val selectedTab: Tab = Tab.IMAGE, + val size: Int = 16, + val model: InPaintModel = InPaintModel(), + val zoomScale: Float = 1f, + val zoomOffsetX: Float = 0f, + val zoomOffsetY: Float = 0f, + val isDrawing: Boolean = false, +) : MviState { + + val isZoomed: Boolean + get() = zoomScale > 1.05f + + enum class Tab( + @StringRes val label: Int, + @DrawableRes val iconRes: Int, + ) { + IMAGE( + LocalizationR.string.in_paint_tab_1, + PresentationR.drawable.ic_image, + ), + FORM( + LocalizationR.string.in_paint_tab_2, + PresentationR.drawable.ic_image, + ); + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintStateProducer.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintStateProducer.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintStateProducer.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintStateProducer.kt index 0aa615c74..1601d8bf1 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintStateProducer.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintStateProducer.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint +package dev.minios.pdaiv1.presentation.screen.inpaint import android.graphics.Bitmap -import com.shifthackz.aisdv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.model.InPaintModel import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.subjects.BehaviorSubject diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintViewModel.kt similarity index 77% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintViewModel.kt index 9fdaa7585..7543549a9 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/InPaintViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/InPaintViewModel.kt @@ -1,12 +1,12 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint - -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter +package dev.minios.pdaiv1.presentation.screen.inpaint + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter import com.shifthackz.android.core.mvi.EmptyEffect import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.kotlin.subscribeBy @@ -120,6 +120,22 @@ class InPaintViewModel( model = state.model.copy(maskMode = intent.value) ) } + + InPaintIntent.Action.ToggleDrawing -> updateState { state -> + state.copy(isDrawing = !state.isDrawing) + } + + InPaintIntent.Action.ResetZoom -> updateState { state -> + state.copy(zoomScale = 1f, zoomOffsetX = 0f, zoomOffsetY = 0f) + } + + is InPaintIntent.UpdateZoomScale -> updateState { state -> + state.copy(zoomScale = intent.scale) + } + + is InPaintIntent.UpdateZoomOffset -> updateState { state -> + state.copy(zoomOffsetX = intent.offsetX, zoomOffsetY = intent.offsetY) + } } } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/components/CapSizeSlider.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/components/CapSizeSlider.kt similarity index 93% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/components/CapSizeSlider.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/components/CapSizeSlider.kt index 38ffe76a4..d36fb0c3d 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/components/CapSizeSlider.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/components/CapSizeSlider.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint.components +package dev.minios.pdaiv1.presentation.screen.inpaint.components import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -20,8 +20,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.theme.sliderColors -import com.shifthackz.aisdv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.presentation.utils.Constants import kotlin.math.abs import kotlin.math.roundToInt diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/components/InPaintComponent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/components/InPaintComponent.kt new file mode 100644 index 000000000..5eadcd142 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/components/InPaintComponent.kt @@ -0,0 +1,251 @@ +package dev.minios.pdaiv1.presentation.screen.inpaint.components + +import android.graphics.Bitmap +import android.graphics.Picture +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image +import androidx.compose.foundation.gestures.detectTransformGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.draw +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.model.MotionEvent +import com.smarttoolfactory.gesture.pointerMotionEvents +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +@Composable +fun InPaintComponent( + modifier: Modifier = Modifier, + drawMode: Boolean = false, + inPaint: InPaintModel = InPaintModel(), + bitmap: Bitmap? = null, + capWidth: Int = 16, + scale: Float = 1f, + offsetX: Float = 0f, + offsetY: Float = 0f, + onScaleChanged: (Float) -> Unit = {}, + onOffsetChanged: (Float, Float) -> Unit = { _, _ -> }, + onPathDrawn: (Path) -> Unit = {}, + onPathBitmapDrawn: (Bitmap?) -> Unit = {}, +) { + val scope = rememberCoroutineScope() + val aspectRatio = bitmap?.let { + if (it.width > 0 && it.height > 0) it.width.toFloat() / it.height.toFloat() + else 1f + } ?: 1f + + // Local zoom state for smooth gestures + var localScale by remember { mutableFloatStateOf(scale) } + var localOffsetX by remember { mutableFloatStateOf(offsetX) } + var localOffsetY by remember { mutableFloatStateOf(offsetY) } + + // Sync with external state + LaunchedEffect(scale, offsetX, offsetY) { + localScale = scale + localOffsetX = offsetX + localOffsetY = offsetY + } + + Box( + modifier = modifier + .fillMaxWidth() + .aspectRatio(aspectRatio) + .clipToBounds() + .let { + if (!drawMode) { + it.pointerInput(Unit) { + detectTransformGestures { _, pan, zoom, _ -> + val newScale = (localScale * zoom).coerceIn(1f, 5f) + localScale = newScale + onScaleChanged(newScale) + if (newScale > 1f) { + localOffsetX += pan.x + localOffsetY += pan.y + onOffsetChanged(localOffsetX, localOffsetY) + } else { + localOffsetX = 0f + localOffsetY = 0f + onOffsetChanged(0f, 0f) + } + } + } + } else { + it + } + }, + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .fillMaxSize() + .graphicsLayer( + scaleX = localScale, + scaleY = localScale, + translationX = localOffsetX, + translationY = localOffsetY, + ), + ) { + bitmap?.takeIf { it.width != 0 && it.height != 0 }?.asImageBitmap()?.let { + Image( + modifier = Modifier.fillMaxSize(), + bitmap = it, + contentDescription = null, + contentScale = ContentScale.Fit, + ) + } + inPaint.bitmap?.takeIf { it.width != 0 && it.height != 0 }?.asImageBitmap()?.let { + Image( + modifier = Modifier + .fillMaxSize(), + bitmap = it, + contentDescription = null, + contentScale = ContentScale.Fit, + ) + } + var motionEvent by remember { mutableStateOf(MotionEvent.Idle) } + var currentPosition by remember { mutableStateOf(Offset.Unspecified) } + var previousPosition by remember { mutableStateOf(Offset.Unspecified) } + var currentPath by remember { mutableStateOf(Path()) } + val picture = remember { Picture() } + LaunchedEffect(inPaint.paths.size) { + if (picture.width != 0 && picture.height != 0) { + val pathBmp = createBitmapFromPicture(picture) + val resized = bitmap?.let { + Bitmap.createScaledBitmap(pathBmp, it.width, it.height, false) + } + onPathBitmapDrawn(resized ?: pathBmp) + } else { + onPathBitmapDrawn(null) + } + } + Canvas( + modifier = Modifier + .alpha(0.7f) + .fillMaxSize() + .drawWithCache { + val width = this.size.width.toInt() + val height = this.size.height.toInt() + onDrawWithContent { + val pictureCanvas = androidx.compose.ui.graphics.Canvas( + picture.beginRecording(width, height) + ) + draw(this, this.layoutDirection, pictureCanvas, this.size) { + this@onDrawWithContent.drawContent() + } + picture.endRecording() + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + } + .let { + if (drawMode) it.pointerMotionEvents( + onDown = { pointerInputChange: PointerInputChange -> + currentPosition = pointerInputChange.position + motionEvent = MotionEvent.Down + pointerInputChange.consume() + }, + onMove = { pointerInputChange: PointerInputChange -> + currentPosition = pointerInputChange.position + motionEvent = MotionEvent.Move + pointerInputChange.consume() + }, + onUp = { pointerInputChange: PointerInputChange -> + motionEvent = MotionEvent.Up + pointerInputChange.consume() + }, + delayAfterDownInMillis = 25L, + ) + else it + }, + ) { + if (drawMode) { + when (motionEvent) { + MotionEvent.Down -> { + currentPath.moveTo(currentPosition.x, currentPosition.y) + previousPosition = currentPosition + } + + MotionEvent.Move -> { + currentPath.quadraticTo( + previousPosition.x, + previousPosition.y, + (previousPosition.x + currentPosition.x) / 2, + (previousPosition.y + currentPosition.y) / 2, + ) + previousPosition = currentPosition + } + + MotionEvent.Up -> { + currentPath.lineTo(currentPosition.x, currentPosition.y) + currentPosition = Offset.Unspecified + previousPosition = currentPosition + motionEvent = MotionEvent.Idle + scope.launch { + onPathDrawn(currentPath) + delay(100L) + currentPath = Path() + } + } + + else -> Unit + } + } + + val draw: (Path, Int) -> Unit = { path, cap -> + drawPath( + color = Color.White, + path = path, + style = Stroke( + width = cap.dp.toPx(), + cap = StrokeCap.Round, + join = StrokeJoin.Round, + ) + ) + } + + inPaint.paths.forEach { (p, c) -> draw(p, c) } + draw(currentPath, capWidth) + } + } + } +} + +private fun createBitmapFromPicture(picture: Picture): Bitmap { + val bitmap = Bitmap.createBitmap( + picture.width, + picture.height, + Bitmap.Config.ARGB_8888 + ) + val canvas = android.graphics.Canvas(bitmap) + canvas.drawPicture(picture) + return bitmap +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/forms/ImageDrawForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/forms/ImageDrawForm.kt new file mode 100644 index 000000000..1aca215e6 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/forms/ImageDrawForm.kt @@ -0,0 +1,100 @@ +package dev.minios.pdaiv1.presentation.screen.inpaint.forms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Draw +import androidx.compose.material.icons.filled.ZoomOutMap +import androidx.compose.material.icons.outlined.Draw +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintIntent +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintState +import dev.minios.pdaiv1.presentation.screen.inpaint.components.InPaintComponent + +@Composable +fun ImageDrawForm( + modifier: Modifier = Modifier, + state: InPaintState = InPaintState(), + processIntent: (InPaintIntent) -> Unit = {}, +) { + Box( + modifier = modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface), + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + InPaintComponent( + drawMode = state.isDrawing, + inPaint = state.model, + bitmap = state.bitmap, + capWidth = state.size, + scale = state.zoomScale, + offsetX = state.zoomOffsetX, + offsetY = state.zoomOffsetY, + onScaleChanged = { processIntent(InPaintIntent.UpdateZoomScale(it)) }, + onOffsetChanged = { x, y -> processIntent(InPaintIntent.UpdateZoomOffset(x, y)) }, + onPathDrawn = { processIntent(InPaintIntent.DrawPath(it)) }, + onPathBitmapDrawn = { processIntent(InPaintIntent.DrawPathBmp(it)) }, + ) + } + + // Control buttons overlay + Row( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + FilledTonalIconButton( + onClick = { processIntent(InPaintIntent.Action.ResetZoom) }, + modifier = Modifier.size(40.dp), + shape = CircleShape, + enabled = state.isZoomed, + ) { + Icon( + imageVector = Icons.Default.ZoomOutMap, + contentDescription = "Reset Zoom", + ) + } + if (state.isDrawing) { + FilledIconButton( + onClick = { processIntent(InPaintIntent.Action.ToggleDrawing) }, + modifier = Modifier.size(40.dp), + shape = CircleShape, + ) { + Icon( + imageVector = Icons.Default.Draw, + contentDescription = "Drawing Mode", + ) + } + } else { + FilledTonalIconButton( + onClick = { processIntent(InPaintIntent.Action.ToggleDrawing) }, + modifier = Modifier.size(40.dp), + shape = CircleShape, + ) { + Icon( + imageVector = Icons.Outlined.Draw, + contentDescription = "Drawing Mode", + ) + } + } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/forms/InPaintParamsForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/forms/InPaintParamsForm.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/forms/InPaintParamsForm.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/forms/InPaintParamsForm.kt index 19650c740..da6e71908 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/inpaint/forms/InPaintParamsForm.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/inpaint/forms/InPaintParamsForm.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.inpaint.forms +package dev.minios.pdaiv1.presentation.screen.inpaint.forms import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -17,13 +17,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.model.InPaintModel -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintIntent -import com.shifthackz.aisdv1.presentation.theme.sliderColors -import com.shifthackz.aisdv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintIntent +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.presentation.utils.Constants import kotlin.math.abs import kotlin.math.roundToInt -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable @Preview diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderScreen.kt similarity index 93% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderScreen.kt index ac0c74d74..959b25767 100755 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/loader/ConfigurationLoaderScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderScreen.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.loader +package dev.minios.pdaiv1.presentation.screen.loader import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -17,8 +17,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText import com.shifthackz.android.core.mvi.MviComponent import org.koin.androidx.compose.koinViewModel diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderState.kt new file mode 100644 index 000000000..f20ef985a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderState.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.presentation.screen.loader + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.core.model.UiText +import com.shifthackz.android.core.mvi.MviState + +interface ConfigurationLoaderState : MviState { + + @Immutable + data class StatusNotification(val statusNotification: UiText) : ConfigurationLoaderState +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderViewModel.kt new file mode 100755 index 000000000..8d3e0cbb8 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/loader/ConfigurationLoaderViewModel.kt @@ -0,0 +1,59 @@ +package dev.minios.pdaiv1.presentation.screen.loader + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.usecase.caching.DataPreLoaderUseCase +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import com.shifthackz.android.core.mvi.EmptyEffect +import com.shifthackz.android.core.mvi.EmptyIntent +import io.reactivex.rxjava3.kotlin.subscribeBy +import java.util.concurrent.TimeUnit +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +class ConfigurationLoaderViewModel( + dataPreLoaderUseCase: DataPreLoaderUseCase, + dispatchersProvider: DispatchersProvider, + schedulersProvider: SchedulersProvider, + mainRouter: MainRouter, +) : MviRxViewModel() { + + override val initialState = ConfigurationLoaderState.StatusNotification( + LocalizationR.string.splash_status_initializing.asUiText() + ) + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !dataPreLoaderUseCase() + .timeout(15L, TimeUnit.SECONDS) + .doOnSubscribe { + updateState { + ConfigurationLoaderState.StatusNotification( + LocalizationR.string.splash_status_fetching.asUiText() + ) + } + } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + updateState { + ConfigurationLoaderState.StatusNotification("Failed loading data".asUiText()) + } + mainRouter.navigateToHomeScreen() + errorLog(t) + }, + onComplete = { + updateState { + ConfigurationLoaderState.StatusNotification( + LocalizationR.string.splash_status_launching.asUiText() + ) + } + mainRouter.navigateToHomeScreen() + }, + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerEffect.kt new file mode 100644 index 000000000..90407d238 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerEffect.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.presentation.screen.logger + +import com.shifthackz.android.core.mvi.MviEffect + +sealed interface LoggerEffect : MviEffect { + data class CopyToClipboard(val text: String) : LoggerEffect + data class ShareLog(val text: String) : LoggerEffect +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerIntent.kt new file mode 100644 index 000000000..7bfc488a9 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerIntent.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.presentation.screen.logger + +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface LoggerIntent : MviIntent { + + data object ReadLogs : LoggerIntent + + data object CopyLogs : LoggerIntent + + data object ShareLogs : LoggerIntent + + data object NavigateBack : LoggerIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerScreen.kt new file mode 100644 index 000000000..d62abdbd8 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerScreen.kt @@ -0,0 +1,252 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package dev.minios.pdaiv1.presentation.screen.logger + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.ArrowDownward +import androidx.compose.material.icons.filled.ArrowUpward +import androidx.compose.material.icons.filled.ContentCopy +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Save +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.minios.pdaiv1.core.common.extensions.copyToClipboard +import com.shifthackz.android.core.mvi.MviComponent +import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel +import android.content.Intent +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun LoggerScreen() { + val context = LocalContext.current + MviComponent( + viewModel = koinViewModel(), + processEffect = { effect -> + when (effect) { + is LoggerEffect.CopyToClipboard -> { + context.copyToClipboard(effect.text) + } + is LoggerEffect.ShareLog -> { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, effect.text) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + context.startActivity(shareIntent) + } + } + } + ) { state, processIntent -> + LoggerScreenContent( + state = state, + processIntent = processIntent, + ) + } +} + +@Composable +@Preview +private fun LoggerScreenContent( + state: LoggerState = LoggerState(), + processIntent: (LoggerIntent) -> Unit = {}, +) { + val scrollState = rememberScrollState() + val scope = rememberCoroutineScope() + Scaffold( + topBar = { + CenterAlignedTopAppBar( + navigationIcon = { + IconButton( + onClick = { processIntent(LoggerIntent.NavigateBack) }, + content = { + Icon( + Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = "Back button", + ) + }, + ) + }, + title = { + Text( + text = stringResource(id = LocalizationR.string.title_debug_logger), + style = MaterialTheme.typography.headlineMedium, + ) + }, + actions = { + AnimatedVisibility( + visible = !state.loading, + enter = fadeIn(), + exit = fadeOut(), + ) { + Row { + IconButton( + onClick = { + processIntent(LoggerIntent.CopyLogs) + }, + content = { + Icon( + Icons.Default.ContentCopy, + contentDescription = "Copy", + ) + }, + ) + IconButton( + onClick = { + processIntent(LoggerIntent.ShareLogs) + }, + content = { + Icon( + Icons.Default.Save, + contentDescription = "Save", + ) + }, + ) + IconButton( + onClick = { + processIntent(LoggerIntent.ReadLogs) + }, + content = { + Icon( + Icons.Default.Refresh, + contentDescription = "Refresh", + ) + }, + ) + } + } + } + ) + }, + bottomBar = { + AnimatedVisibility( + visible = !state.loading && state.text.isNotBlank(), + enter = fadeIn(), + exit = fadeOut(), + ) { + Row( + modifier = Modifier + .navigationBarsPadding() + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), + ) { + IconButton( + onClick = { + scope.launch { + scrollState.animateScrollTo(0) + } + }, + content = { + Icon( + Icons.Default.ArrowUpward, + contentDescription = "Down", + ) + }, + ) + IconButton( + onClick = { + scope.launch { + scrollState.animateScrollTo(scrollState.maxValue) + } + }, + content = { + Icon( + Icons.Default.ArrowDownward, + contentDescription = "Down", + ) + }, + ) + } + } + + } + ) { paddingValues -> + val text = if (!state.loading) state.text else "" + val scrollStateHorizontal = rememberScrollState() + if (!state.loading && state.text.isBlank()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(id = LocalizationR.string.debug_logger_empty), + textAlign = TextAlign.Center, + ) + } + } + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .verticalScroll(scrollState), + ) { + AnimatedVisibility( + visible = state.loading, + enter = fadeIn(), + exit = fadeOut(), + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + modifier = Modifier + .size(60.dp) + .aspectRatio(1f), + ) + } + } + Text( + modifier = Modifier.horizontalScroll(scrollStateHorizontal), + text = text, + fontFamily = FontFamily.Monospace, + fontSize = 11.sp, + lineHeight = 12.sp, + ) + } + LaunchedEffect(state.text) { + if (!state.loading) { + scrollState.scrollTo(scrollState.maxValue) + } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerState.kt similarity index 77% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerState.kt index 0571411aa..ce92280a2 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerState.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.logger +package dev.minios.pdaiv1.presentation.screen.logger import androidx.compose.runtime.Immutable import com.shifthackz.android.core.mvi.MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerViewModel.kt new file mode 100644 index 000000000..a5bdc784a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/logger/LoggerViewModel.kt @@ -0,0 +1,52 @@ +package dev.minios.pdaiv1.presentation.screen.logger + +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.FileLoggingTree +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import java.io.File + +class LoggerViewModel( + dispatchersProvider: DispatchersProvider, + private val fileProviderDescriptor: FileProviderDescriptor, + private val mainRouter: MainRouter, +) : MviRxViewModel() { + + override val initialState = LoggerState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + readLogs() + } + + override fun processIntent(intent: LoggerIntent) { + when (intent) { + LoggerIntent.ReadLogs -> readLogs() + LoggerIntent.CopyLogs -> emitEffect(LoggerEffect.CopyToClipboard(state.value.text)) + LoggerIntent.ShareLogs -> emitEffect(LoggerEffect.ShareLog(state.value.text)) + LoggerIntent.NavigateBack -> mainRouter.navigateBack() + } + } + + private fun readLogs() { + updateState { it.copy(loading = true, text = "") } + try { + val logFile = File( + fileProviderDescriptor.logsCacheDirPath + + "/" + + FileLoggingTree.LOGGER_FILENAME + ) + val content = logFile.readText() + updateState { + it.copy( + loading = false, + text = content, + ) + } + } catch (e: Exception) { + updateState { it.copy(loading = false) } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingDensity.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingDensity.kt similarity index 85% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingDensity.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingDensity.kt index 9ee89f1e0..1c3a41eca 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingDensity.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingDensity.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding +package dev.minios.pdaiv1.presentation.screen.onboarding import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingIntent.kt new file mode 100644 index 000000000..30badfd73 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingIntent.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.presentation.screen.onboarding + +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface OnBoardingIntent : MviIntent { + data object Navigate : OnBoardingIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingPage.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingPage.kt new file mode 100644 index 000000000..0f5451261 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingPage.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.presentation.screen.onboarding + + +enum class OnBoardingPage { + Providers, + Form, + LocalDiffusion, + LookAndFeel, +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingScreen.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingScreen.kt index 2352d6bf1..c04e52f8f 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingScreen.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding +package dev.minios.pdaiv1.presentation.screen.onboarding import androidx.activity.compose.BackHandler import androidx.compose.animation.core.animateFloatAsState @@ -37,11 +37,11 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.rotate import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.screen.onboarding.page.FormPageContent -import com.shifthackz.aisdv1.presentation.screen.onboarding.page.LocalDiffusionPageContent -import com.shifthackz.aisdv1.presentation.screen.onboarding.page.LookAndFeelPageContent -import com.shifthackz.aisdv1.presentation.screen.onboarding.page.ProviderPageContent +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.screen.onboarding.page.FormPageContent +import dev.minios.pdaiv1.presentation.screen.onboarding.page.LocalDiffusionPageContent +import dev.minios.pdaiv1.presentation.screen.onboarding.page.LookAndFeelPageContent +import dev.minios.pdaiv1.presentation.screen.onboarding.page.ProviderPageContent import com.shifthackz.android.core.mvi.MviComponent import kotlinx.coroutines.Job import kotlinx.coroutines.launch diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingState.kt new file mode 100644 index 000000000..06952606a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingState.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.presentation.screen.onboarding + +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import com.shifthackz.android.core.mvi.MviState + +data class OnBoardingState( + val darkThemeToken: DarkThemeToken = DarkThemeToken.FRAPPE, + val appVersion: String = "", +) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingText.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingText.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingText.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingText.kt index a40aed1e8..7227975fa 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/OnBoardingText.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingText.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding +package dev.minios.pdaiv1.presentation.screen.onboarding import androidx.annotation.StringRes import androidx.compose.runtime.Composable diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingViewModel.kt new file mode 100644 index 000000000..2417764ca --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/OnBoardingViewModel.kt @@ -0,0 +1,59 @@ +package dev.minios.pdaiv1.presentation.screen.onboarding + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.splash.SplashNavigationUseCase +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.postSplashNavigation +import com.shifthackz.android.core.mvi.EmptyEffect +import io.reactivex.rxjava3.kotlin.subscribeBy + +class OnBoardingViewModel( + val launchSource: LaunchSource, + dispatchersProvider: DispatchersProvider, + private val mainRouter: MainRouter, + private val splashNavigationUseCase: SplashNavigationUseCase, + private val preferenceManager: PreferenceManager, + private val schedulersProvider: SchedulersProvider, + private val buildInfoProvider: BuildInfoProvider, +) : MviRxViewModel() { + + override val initialState = OnBoardingState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + updateState { + val token = DarkThemeToken.parse(preferenceManager.designDarkThemeToken) + val version = buildInfoProvider.toString() + it.copy( + darkThemeToken = token, + appVersion = version + ) + } + } + + override fun processIntent(intent: OnBoardingIntent) { + when (intent) { + OnBoardingIntent.Navigate -> { + preferenceManager.onBoardingComplete = true + when (launchSource) { + LaunchSource.SPLASH -> !splashNavigationUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { action -> + mainRouter.postSplashNavigation(action) + } + + LaunchSource.SETTINGS -> mainRouter.navigateBack() + } + } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/FormPageContent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/FormPageContent.kt similarity index 81% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/FormPageContent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/FormPageContent.kt index 4e1f8b31a..d2e344a23 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/FormPageContent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/FormPageContent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding.page +package dev.minios.pdaiv1.presentation.screen.onboarding.page import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box @@ -18,17 +18,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.extensions.gesturesDisabled -import com.shifthackz.aisdv1.presentation.screen.onboarding.buildOnBoardingText -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingDensity -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction -import com.shifthackz.aisdv1.presentation.screen.txt2img.TextToImageScreenContent -import com.shifthackz.aisdv1.presentation.screen.txt2img.TextToImageState -import com.shifthackz.aisdv1.presentation.widget.frame.PhoneFrame +import dev.minios.pdaiv1.core.extensions.gesturesDisabled +import dev.minios.pdaiv1.presentation.screen.onboarding.buildOnBoardingText +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingDensity +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction +import dev.minios.pdaiv1.presentation.screen.txt2img.TextToImageScreenContent +import dev.minios.pdaiv1.presentation.screen.txt2img.TextToImageState +import dev.minios.pdaiv1.presentation.widget.frame.PhoneFrame import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun FormPageContent( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/LocalDiffusionPageContent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/LocalDiffusionPageContent.kt similarity index 78% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/LocalDiffusionPageContent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/LocalDiffusionPageContent.kt index e410c2a71..dab127e0c 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/LocalDiffusionPageContent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/LocalDiffusionPageContent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding.page +package dev.minios.pdaiv1.presentation.screen.onboarding.page import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -17,17 +17,17 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.extensions.gesturesDisabled -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.screen.onboarding.buildOnBoardingText -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingDensity -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction -import com.shifthackz.aisdv1.presentation.screen.txt2img.TextToImageScreenContent -import com.shifthackz.aisdv1.presentation.screen.txt2img.TextToImageState -import com.shifthackz.aisdv1.presentation.widget.dialog.GeneratingProgressDialogContent -import com.shifthackz.aisdv1.presentation.widget.frame.PhoneFrame -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.extensions.gesturesDisabled +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.screen.onboarding.buildOnBoardingText +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingDensity +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction +import dev.minios.pdaiv1.presentation.screen.txt2img.TextToImageScreenContent +import dev.minios.pdaiv1.presentation.screen.txt2img.TextToImageState +import dev.minios.pdaiv1.presentation.widget.dialog.GeneratingProgressDialogContent +import dev.minios.pdaiv1.presentation.widget.frame.PhoneFrame +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun LocalDiffusionPageContent( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/LookAndFeelPageContent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/LookAndFeelPageContent.kt similarity index 76% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/LookAndFeelPageContent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/LookAndFeelPageContent.kt index 37df5acda..58d692ee8 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/LookAndFeelPageContent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/LookAndFeelPageContent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding.page +package dev.minios.pdaiv1.presentation.screen.onboarding.page import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -21,23 +21,23 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.extensions.gesturesDisabled -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.presentation.screen.onboarding.buildOnBoardingText -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingDensity -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction -import com.shifthackz.aisdv1.presentation.screen.settings.SettingsScreenContent -import com.shifthackz.aisdv1.presentation.screen.settings.SettingsState -import com.shifthackz.aisdv1.presentation.theme.global.AiSdAppTheme -import com.shifthackz.aisdv1.presentation.theme.global.AiSdAppThemeState -import com.shifthackz.aisdv1.presentation.theme.isSdAppInDarkTheme -import com.shifthackz.aisdv1.presentation.widget.frame.PhoneFrame +import dev.minios.pdaiv1.core.extensions.gesturesDisabled +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.presentation.screen.onboarding.buildOnBoardingText +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingDensity +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction +import dev.minios.pdaiv1.presentation.screen.settings.SettingsScreenContent +import dev.minios.pdaiv1.presentation.screen.settings.SettingsState +import dev.minios.pdaiv1.presentation.theme.global.AiSdAppTheme +import dev.minios.pdaiv1.presentation.theme.global.AiSdAppThemeState +import dev.minios.pdaiv1.presentation.theme.isSdAppInDarkTheme +import dev.minios.pdaiv1.presentation.widget.frame.PhoneFrame import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun LookAndFeelPageContent( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/ProvidersPageContent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/ProvidersPageContent.kt similarity index 77% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/ProvidersPageContent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/ProvidersPageContent.kt index 6c2a881e1..6cab33dde 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/onboarding/page/ProvidersPageContent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/onboarding/page/ProvidersPageContent.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.onboarding.page +package dev.minios.pdaiv1.presentation.screen.onboarding.page import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -21,18 +21,18 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.extensions.gesturesDisabled -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.screen.onboarding.buildOnBoardingText -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingDensity -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio -import com.shifthackz.aisdv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupScreenContent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.widget.frame.PhoneFrame +import dev.minios.pdaiv1.core.extensions.gesturesDisabled +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.screen.onboarding.buildOnBoardingText +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingDensity +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneAspectRatio +import dev.minios.pdaiv1.presentation.screen.onboarding.onBoardingPhoneWidthFraction +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupScreenContent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.widget.frame.PhoneFrame import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ProviderPageContent( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportIntent.kt similarity index 75% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportIntent.kt index 60cb4ed1f..e23161c29 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportIntent.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.presentation.screen.report +package dev.minios.pdaiv1.presentation.screen.report -import com.shifthackz.aisdv1.domain.entity.ReportReason +import dev.minios.pdaiv1.domain.entity.ReportReason import com.shifthackz.android.core.mvi.MviIntent sealed interface ReportIntent : MviIntent { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportScreen.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportScreen.kt index f98ab6e73..3109ba2e1 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) -package com.shifthackz.aisdv1.presentation.screen.report +package dev.minios.pdaiv1.presentation.screen.report import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.Image @@ -50,11 +50,11 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.domain.entity.ReportReason -import com.shifthackz.aisdv1.core.localization.R as LocalizationR -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.theme.isSdAppInDarkTheme -import com.shifthackz.aisdv1.presentation.widget.input.chip.ChipTextFieldItem +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.theme.isSdAppInDarkTheme +import dev.minios.pdaiv1.presentation.widget.input.chip.ChipTextFieldItem import com.shifthackz.android.core.mvi.MviComponent @Composable diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportState.kt new file mode 100644 index 000000000..c4f4ad5cd --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportState.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.presentation.screen.report + +import android.graphics.Bitmap +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.presentation.model.Modal +import com.shifthackz.android.core.mvi.MviState + +data class ReportState( + val loading: Boolean = true, + val screenModal: Modal = Modal.None, + val imageBitmap: Bitmap? = null, + val imageBase64: String = "", + val text: String = "", + val reason: ReportReason = ReportReason.Other, + val reportSent: Boolean = false, +) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportViewModel.kt similarity index 76% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportViewModel.kt index 6599d4587..dfdf7d17c 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/report/ReportViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/report/ReportViewModel.kt @@ -1,20 +1,20 @@ -package com.shifthackz.aisdv1.presentation.screen.report +package dev.minios.pdaiv1.presentation.screen.report -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.appbuild.BuildType -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.entity.ReportReason -import com.shifthackz.aisdv1.domain.usecase.caching.GetLastResultFromCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultUseCase -import com.shifthackz.aisdv1.domain.usecase.report.SendReportUseCase -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.entity.ReportReason +import dev.minios.pdaiv1.domain.usecase.caching.GetLastResultFromCacheUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultUseCase +import dev.minios.pdaiv1.domain.usecase.report.SendReportUseCase +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter import com.shifthackz.android.core.mvi.EmptyEffect import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.kotlin.subscribeBy diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsEffect.kt similarity index 87% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsEffect.kt index f43aeca44..70760108b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsEffect.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.settings +package dev.minios.pdaiv1.presentation.screen.settings import com.shifthackz.android.core.mvi.MviEffect diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsIntent.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsIntent.kt index 41f68563b..bcdafa15a 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsIntent.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.presentation.screen.settings +package dev.minios.pdaiv1.presentation.screen.settings -import com.shifthackz.aisdv1.core.common.links.LinksProvider -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.core.common.links.LinksProvider +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent import com.shifthackz.android.core.mvi.MviIntent import org.koin.core.component.KoinComponent import org.koin.core.component.inject diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsScreen.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsScreen.kt index 1942f01c6..2dc60be7c 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.screen.settings +package dev.minios.pdaiv1.presentation.screen.settings import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -50,7 +50,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -62,29 +61,30 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.extensions.openUrl -import com.shifthackz.aisdv1.core.common.extensions.showToast -import com.shifthackz.aisdv1.core.common.math.roundTo -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asUiText +import dev.minios.pdaiv1.core.common.extensions.openUrl +import dev.minios.pdaiv1.core.common.extensions.showToast +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.theme.colorTokenPalette -import com.shifthackz.aisdv1.presentation.theme.isSdAppInDarkTheme -import com.shifthackz.aisdv1.presentation.utils.PermissionUtil -import com.shifthackz.aisdv1.presentation.utils.ReportProblemEmailComposer -import com.shifthackz.aisdv1.presentation.widget.color.AccentColorSelector -import com.shifthackz.aisdv1.presentation.widget.color.DarkThemeColorSelector -import com.shifthackz.aisdv1.presentation.widget.item.GridIcon -import com.shifthackz.aisdv1.presentation.widget.item.SettingsHeader -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItemContent -import com.shifthackz.aisdv1.presentation.widget.work.BackgroundWorkWidget +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.theme.colorTokenPalette +import dev.minios.pdaiv1.presentation.theme.isSdAppInDarkTheme +import dev.minios.pdaiv1.presentation.utils.PermissionUtil +import dev.minios.pdaiv1.presentation.utils.ReportProblemEmailComposer +import dev.minios.pdaiv1.presentation.widget.color.AccentColorSelector +import dev.minios.pdaiv1.presentation.widget.color.DarkThemeColorSelector +import dev.minios.pdaiv1.presentation.widget.item.GridIcon +import dev.minios.pdaiv1.presentation.widget.item.SettingsHeader +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.presentation.widget.item.SettingsItemContent +import dev.minios.pdaiv1.presentation.widget.scaffold.CollapsibleScaffold +import dev.minios.pdaiv1.presentation.widget.work.BackgroundWorkWidget import com.shifthackz.android.compose.daynightswitch.DayNightSwitch import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun SettingsScreen() { @@ -145,44 +145,32 @@ fun SettingsScreenContent( processIntent: (SettingsIntent) -> Unit = {}, ) { Box(modifier) { - Scaffold( - topBar = { - Column { - CenterAlignedTopAppBar( - navigationIcon = { - IconButton(onClick = { - processIntent(SettingsIntent.Drawer(DrawerIntent.Open)) - }) { - Icon( - imageVector = Icons.Default.Menu, - contentDescription = "Menu", - ) - } - }, - title = { - Text( - text = stringResource(id = LocalizationR.string.title_settings), - style = MaterialTheme.typography.headlineMedium, + CollapsibleScaffold( + bottomNavBarHeight = if (state.onBoardingDemo) 0.dp else 80.dp, + topBarContent = { + CenterAlignedTopAppBar( + navigationIcon = { + IconButton(onClick = { + processIntent(SettingsIntent.Drawer(DrawerIntent.Open)) + }) { + Icon( + imageVector = Icons.Default.Menu, + contentDescription = "Menu", ) - }, - windowInsets = WindowInsets(0, 0, 0, 0), - ) - BackgroundWorkWidget( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .padding(vertical = 4.dp), - ) - } + } + }, + title = { + Text( + text = stringResource(id = LocalizationR.string.title_settings), + style = MaterialTheme.typography.headlineMedium, + ) + }, + windowInsets = WindowInsets(0, 0, 0, 0), + ) }, - content = { paddingValues -> + contentScrollable = { ContentSettingsState( - modifier = Modifier - .padding( - horizontal = paddingValues.calculateStartPadding( - LocalLayoutDirection.current, - ), - ) - .padding(horizontal = 16.dp), + modifier = Modifier.padding(horizontal = 16.dp), state = state, processIntent = processIntent, ) @@ -205,9 +193,8 @@ private fun ContentSettingsState( isDark = isDark, darkThemeToken = state.darkThemeToken, ) - val scrollState = rememberScrollState() Column( - modifier = modifier.verticalScroll(scrollState), + modifier = modifier, ) { val headerModifier = Modifier.padding(top = 28.dp, bottom = 8.dp) @@ -237,8 +224,10 @@ private fun ContentSettingsState( ServerSource.HUGGING_FACE -> LocalizationR.string.srv_type_hugging_face_short ServerSource.OPEN_AI -> LocalizationR.string.srv_type_open_ai ServerSource.STABILITY_AI -> LocalizationR.string.srv_type_stability_ai + ServerSource.FAL_AI -> LocalizationR.string.srv_type_fal_ai ServerSource.LOCAL_MICROSOFT_ONNX -> LocalizationR.string.srv_type_local_short ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> LocalizationR.string.srv_type_media_pipe_short + ServerSource.LOCAL_QUALCOMM_QNN -> LocalizationR.string.srv_type_qnn_short ServerSource.SWARM_UI -> LocalizationR.string.srv_type_swarm_ui }.asUiText(), onClick = { processIntent(SettingsIntent.NavigateConfiguration) }, @@ -587,13 +576,6 @@ private fun ContentSettingsState( loading = state.loading, text = LocalizationR.string.settings_header_info.asUiText(), ) - SettingsItem( - modifier = itemModifier, - loading = state.loading, - startIcon = Icons.Default.MonetizationOn, - text = LocalizationR.string.settings_item_donate.asUiText(), - onClick = { processIntent(SettingsIntent.Action.Donate) }, - ) SettingsItem( modifier = itemModifier, loading = state.loading, diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsState.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsState.kt index 2ab9a9324..878982678 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsState.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.presentation.screen.settings +package dev.minios.pdaiv1.presentation.screen.settings import androidx.compose.runtime.Immutable -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.entity.Grid -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.model.Modal +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.entity.Grid +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.model.Modal import com.shifthackz.android.core.mvi.MviState @Immutable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsViewModel.kt similarity index 85% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsViewModel.kt index d77565826..ef1ae29f8 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/settings/SettingsViewModel.kt @@ -1,32 +1,32 @@ -package com.shifthackz.aisdv1.presentation.screen.settings - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.extensions.EmptyLambda -import com.shifthackz.aisdv1.core.common.extensions.shouldUseNewMediaStore -import com.shifthackz.aisdv1.core.common.log.errorLog -import com.shifthackz.aisdv1.core.common.model.Quadruple -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.caching.ClearAppCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCase -import com.shifthackz.aisdv1.domain.usecase.stabilityai.ObserveStabilityAiCreditsUseCase -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent +package dev.minios.pdaiv1.presentation.screen.settings + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.extensions.shouldUseNewMediaStore +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.model.Quadruple +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.caching.ClearAppCacheUseCase +import dev.minios.pdaiv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCase +import dev.minios.pdaiv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCase +import dev.minios.pdaiv1.domain.usecase.stabilityai.ObserveStabilityAiCreditsUseCase +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.screen.debug.DebugMenuAccessor +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.kotlin.subscribeBy import java.util.concurrent.TimeUnit -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR class SettingsViewModel( dispatchersProvider: DispatchersProvider, diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupEffect.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupEffect.kt similarity index 83% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupEffect.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupEffect.kt index f1501ff15..9825e5c37 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupEffect.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupEffect.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup +package dev.minios.pdaiv1.presentation.screen.setup import com.shifthackz.android.core.mvi.MviEffect diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupIntent.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupIntent.kt index 9735a3583..654adab97 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupIntent.kt @@ -1,7 +1,7 @@ -package com.shifthackz.aisdv1.presentation.screen.setup +package dev.minios.pdaiv1.presentation.screen.setup -import com.shifthackz.aisdv1.core.common.links.LinksProvider -import com.shifthackz.aisdv1.domain.entity.ServerSource +import dev.minios.pdaiv1.core.common.links.LinksProvider +import dev.minios.pdaiv1.domain.entity.ServerSource import com.shifthackz.android.core.mvi.MviIntent import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -28,20 +28,28 @@ sealed interface ServerSetupIntent : MviIntent { data class UpdateStabilityAiApiKey(val key: String) : ServerSetupIntent + data class UpdateFalAiApiKey(val key: String) : ServerSetupIntent + data class UpdateHuggingFaceApiKey(val key: String) : ServerSetupIntent data class UpdateHuggingFaceModel(val model: String): ServerSetupIntent + data class UpdateFalAiEndpoint(val endpoint: ServerSetupState.FalAiEndpointUi) : ServerSetupIntent + data class UpdateDemoMode(val value: Boolean) : ServerSetupIntent data class UpdateHordeDefaultApiKey(val value: Boolean) : ServerSetupIntent data class SelectLocalModelPath(val value: String) : ServerSetupIntent + data class SelectLocalQnnModelPath(val value: String) : ServerSetupIntent + data class SelectLocalModel(val model: ServerSetupState.LocalModel) : ServerSetupIntent data class AllowLocalCustomModel(val allow: Boolean) : ServerSetupIntent + data object ScanCustomModels : ServerSetupIntent + data object MainButtonClick : ServerSetupIntent data object DismissDialog : ServerSetupIntent @@ -91,6 +99,11 @@ sealed interface ServerSetupIntent : MviIntent { override val url: String get() = linksProvider.stabilityAiInfoUrl } + + data object FalAiInfo : LaunchUrl() { + override val url: String + get() = linksProvider.falAiInfoUrl + } } sealed interface LocalModel : ServerSetupIntent { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupScreen.kt similarity index 84% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupScreen.kt index 0a77f3ad6..23c399e4f 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) -package com.shifthackz.aisdv1.presentation.screen.setup +package dev.minios.pdaiv1.presentation.screen.setup import android.content.Intent import android.os.Build @@ -40,17 +40,17 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.common.extensions.openUrl -import com.shifthackz.aisdv1.core.common.extensions.showToast +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.extensions.openUrl +import dev.minios.pdaiv1.core.common.extensions.showToast import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.modal.ModalRenderer -import com.shifthackz.aisdv1.presentation.screen.setup.components.ConfigurationStepBar -import com.shifthackz.aisdv1.presentation.screen.setup.steps.ConfigurationStep -import com.shifthackz.aisdv1.presentation.screen.setup.steps.SourceSelectionStep -import com.shifthackz.aisdv1.presentation.utils.PermissionUtil -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.screen.setup.components.ConfigurationStepBar +import dev.minios.pdaiv1.presentation.screen.setup.steps.ConfigurationStep +import dev.minios.pdaiv1.presentation.screen.setup.steps.SourceSelectionStep +import dev.minios.pdaiv1.presentation.utils.PermissionUtil +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ServerSetupScreen( @@ -155,12 +155,22 @@ fun ServerSetupScreenContent( onClick = { processIntent(ServerSetupIntent.MainButtonClick) }, enabled = when (state.step) { ServerSetupState.Step.CONFIGURE -> when (state.mode) { - ServerSource.LOCAL_MICROSOFT_ONNX -> state.localOnnxModels.any { - it.downloaded && it.selected + ServerSource.LOCAL_MICROSOFT_ONNX -> if (state.localOnnxCustomModel) { + state.scannedOnnxCustomModels.isNotEmpty() + } else { + state.localOnnxModels.any { it.downloaded } } - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> state.localMediaPipeModels.any { - it.downloaded && it.selected + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> if (state.localMediaPipeCustomModel) { + state.scannedMediaPipeCustomModels.isNotEmpty() + } else { + state.localMediaPipeModels.any { it.downloaded } + } + + ServerSource.LOCAL_QUALCOMM_QNN -> if (state.localQnnCustomModel) { + state.scannedQnnCustomModels.isNotEmpty() + } else { + state.localQnnModels.any { it.downloaded } } else -> true @@ -175,8 +185,9 @@ fun ServerSetupScreenContent( ServerSetupState.Step.SOURCE -> LocalizationR.string.next else -> when (state.mode) { ServerSource.LOCAL_MICROSOFT_ONNX, - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> LocalizationR.string - .action_setup + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE, + ServerSource.LOCAL_QUALCOMM_QNN -> LocalizationR.string + .action_start else -> LocalizationR.string.action_connect } }, diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupScreenTags.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupScreenTags.kt new file mode 100644 index 000000000..23cd56de0 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupScreenTags.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.presentation.screen.setup + +object ServerSetupScreenTags { + const val MAIN_BUTTON = "ServerSetupMainButton" + const val CUSTOM_MODEL_SWITCH = "CustomModelSwitch" +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupState.kt new file mode 100644 index 000000000..4d09c7f02 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupState.kt @@ -0,0 +1,316 @@ +package dev.minios.pdaiv1.presentation.screen.setup + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.core.common.links.LinksProvider +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.setup.mappers.withNewState +import dev.minios.pdaiv1.presentation.utils.Constants +import com.shifthackz.android.core.mvi.MviState +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +@Immutable +data class ServerSetupState( + val showBackNavArrow: Boolean = false, + val onBoardingDemo: Boolean = false, + val step: Step = Step.SOURCE, + val mode: ServerSource = ServerSource.AUTOMATIC1111, + val allowedModes: List = ServerSource.entries, + val screenModal: Modal = Modal.None, + val serverUrl: String = "", + val swarmUiUrl: String = "", + val hordeApiKey: String = "", + val huggingFaceApiKey: String = "", + val openAiApiKey: String = "", + val stabilityAiApiKey: String = "", + val falAiApiKey: String = "", + val falAiEndpoints: List = emptyList(), + val falAiSelectedEndpoint: String = "", + val hordeDefaultApiKey: Boolean = false, + val demoMode: Boolean = false, + val authType: AuthType = AuthType.ANONYMOUS, + val login: String = "", + val password: String = "", + val huggingFaceModels: List = emptyList(), + val huggingFaceModel: String = "", + val localOnnxModels: List = emptyList(), + val localOnnxCustomModel: Boolean = false, + val localOnnxCustomModelPath: String = "", + val scannedOnnxCustomModels: List = emptyList(), + val localMediaPipeModels: List = emptyList(), + val localMediaPipeCustomModel: Boolean = false, + val localMediaPipeCustomModelPath: String = "", + val scannedMediaPipeCustomModels: List = emptyList(), + val localQnnModels: List = emptyList(), + val localQnnCustomModel: Boolean = false, + val localQnnCustomModelPath: String = "", + val scannedQnnCustomModels: List = emptyList(), + val passwordVisible: Boolean = false, + val serverUrlValidationError: UiText? = null, + val swarmUiUrlValidationError: UiText? = null, + val loginValidationError: UiText? = null, + val passwordValidationError: UiText? = null, + val hordeApiKeyValidationError: UiText? = null, + val huggingFaceApiKeyValidationError: UiText? = null, + val openAiApiKeyValidationError: UiText? = null, + val stabilityAiApiKeyValidationError: UiText? = null, + val falAiApiKeyValidationError: UiText? = null, + val localCustomOnnxPathValidationError: UiText? = null, + val localCustomMediaPipePathValidationError: UiText? = null, + val localCustomQnnPathValidationError: UiText? = null, +) : MviState, KoinComponent { + + val localCustomModel: Boolean + get() = when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> localOnnxCustomModel + ServerSource.LOCAL_QUALCOMM_QNN -> localQnnCustomModel + else -> localMediaPipeCustomModel + } + + val localCustomModelPath: String + get() = when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> localOnnxCustomModelPath + ServerSource.LOCAL_QUALCOMM_QNN -> localQnnCustomModelPath + else -> localMediaPipeCustomModelPath + } + + val localModels: List + get() = when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> localOnnxModels + ServerSource.LOCAL_QUALCOMM_QNN -> localQnnModels + else -> localMediaPipeModels + } + + val localCustomModelPathValidationError: UiText? + get() = when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> localCustomOnnxPathValidationError + ServerSource.LOCAL_QUALCOMM_QNN -> localCustomQnnPathValidationError + else -> localCustomMediaPipePathValidationError + } + + val demoModeUrl: String + get() { + val linksProvider: LinksProvider by inject() + return linksProvider.demoModeUrl + } + + fun withHordeApiKey(value: String) = this.copy( + hordeApiKey = value, + hordeDefaultApiKey = value == Constants.HORDE_DEFAULT_API_KEY, + ) + + fun withCredentials(value: AuthorizationCredentials) = when (value) { + is AuthorizationCredentials.HttpBasic -> copy( + login = value.login, + password = value.password, + ) + + AuthorizationCredentials.None -> this + } + + fun withLocalCustomModelPath(value: String): ServerSetupState = when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> copy( + localOnnxCustomModelPath = value, + localCustomOnnxPathValidationError = null, + ) + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> copy( + localMediaPipeCustomModelPath = value, + localCustomMediaPipePathValidationError = null, + ) + + ServerSource.LOCAL_QUALCOMM_QNN -> copy( + localQnnCustomModelPath = value, + localCustomQnnPathValidationError = null, + ) + + else -> this + } + + fun withUpdatedLocalModel(value: LocalModel): ServerSetupState = when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> copy( + localOnnxModels = localOnnxModels.withNewState(value) + ) + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> copy( + localMediaPipeModels = localMediaPipeModels.withNewState(value) + ) + ServerSource.LOCAL_QUALCOMM_QNN -> copy( + localQnnModels = localQnnModels.withNewState(value) + ) + else -> this + } + + fun withDeletedLocalModel(value: LocalModel): ServerSetupState = when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> copy( + screenModal = Modal.None, + localOnnxModels = localOnnxModels.withNewState( + value.copy( + downloadState = DownloadState.Unknown, + downloaded = false, + ), + ) + ) + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> copy( + screenModal = Modal.None, + localMediaPipeModels = localMediaPipeModels.withNewState( + value.copy( + downloadState = DownloadState.Unknown, + downloaded = false, + ), + ) + ) + + ServerSource.LOCAL_QUALCOMM_QNN -> copy( + screenModal = Modal.None, + localQnnModels = localQnnModels.withNewState( + value.copy( + downloadState = DownloadState.Unknown, + downloaded = false, + ), + ) + ) + + else -> copy(screenModal = Modal.None) + } + + fun withSelectedLocalModel(value: LocalModel): ServerSetupState { + fun List.selectModel(): List { + // Find existing model to preserve its downloaded/downloadState + val existing = find { it.id == value.id } + val updatedModel = value.copy( + selected = true, + downloaded = existing?.downloaded ?: value.downloaded, + downloadState = existing?.downloadState ?: value.downloadState, + ) + return withNewState(updatedModel) + } + + // Check if this is a scanned custom model (ID starts with CUSTOM_) + val isScannedCustomModel = value.id.startsWith("CUSTOM_QNN:") || + value.id.startsWith("CUSTOM_ONNX:") || + value.id.startsWith("CUSTOM_MP:") + + return when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> if (isScannedCustomModel) { + copy( + scannedOnnxCustomModels = scannedOnnxCustomModels.selectModel(), + localOnnxModels = localOnnxModels.map { it.copy(selected = false) }, + ) + } else { + copy( + localOnnxModels = localOnnxModels.selectModel(), + scannedOnnxCustomModels = scannedOnnxCustomModels.map { it.copy(selected = false) }, + ) + } + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> if (isScannedCustomModel) { + copy( + scannedMediaPipeCustomModels = scannedMediaPipeCustomModels.selectModel(), + localMediaPipeModels = localMediaPipeModels.map { it.copy(selected = false) }, + ) + } else { + copy( + localMediaPipeModels = localMediaPipeModels.selectModel(), + scannedMediaPipeCustomModels = scannedMediaPipeCustomModels.map { it.copy(selected = false) }, + ) + } + + ServerSource.LOCAL_QUALCOMM_QNN -> if (isScannedCustomModel) { + copy( + scannedQnnCustomModels = scannedQnnCustomModels.selectModel(), + localQnnModels = localQnnModels.map { it.copy(selected = false) }, + ) + } else { + copy( + localQnnModels = localQnnModels.selectModel(), + scannedQnnCustomModels = scannedQnnCustomModels.map { it.copy(selected = false) }, + ) + } + + else -> this + } + } + + fun withAllowCustomModel(value: Boolean): ServerSetupState { + fun List.updateCustomModelSelection(id: String) = withNewState( + find { m -> m.id == id }?.copy(selected = value) + ) + return when (mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> this.copy( + localOnnxCustomModel = value, + localOnnxModels = localOnnxModels.updateCustomModelSelection( + id = LocalAiModel.CustomOnnx.id, + ), + ) + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> this.copy( + localMediaPipeCustomModel = value, + localMediaPipeModels = localMediaPipeModels.updateCustomModelSelection( + id = LocalAiModel.CustomMediaPipe.id, + ), + ) + + ServerSource.LOCAL_QUALCOMM_QNN -> this.copy( + localQnnCustomModel = value, + localQnnModels = localQnnModels.updateCustomModelSelection( + id = LocalAiModel.CustomQnn.id, + ), + ) + + else -> this + } + } + + enum class Step { + SOURCE, + CONFIGURE; + } + + enum class AuthType { + ANONYMOUS, + HTTP_BASIC; + } + + data class LocalModel( + val id: String, + val name: String, + val size: String, + val downloaded: Boolean = false, + val downloadState: DownloadState = DownloadState.Unknown, + val selected: Boolean = false, + val runOnCpu: Boolean = false, + ) + + data class FalAiEndpointUi( + val id: String, + val endpointId: String, + val title: String, + val category: String, + val isCustom: Boolean, + ) +} + +val Configuration.authType: ServerSetupState.AuthType + get() { + val noCredentials = ServerSetupState.AuthType.ANONYMOUS + if (this.demoMode) return noCredentials + if (this.source != ServerSource.AUTOMATIC1111) return noCredentials + return when (this.authCredentials.key) { + AuthorizationCredentials.Key.NONE -> noCredentials + AuthorizationCredentials.Key.HTTP_BASIC -> ServerSetupState.AuthType.HTTP_BASIC + } + } + +fun ServerSetupState.credentialsDomain(): AuthorizationCredentials { + return when (this.authType) { + ServerSetupState.AuthType.ANONYMOUS -> AuthorizationCredentials.None + ServerSetupState.AuthType.HTTP_BASIC -> AuthorizationCredentials.HttpBasic(login, password) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupViewModel.kt new file mode 100644 index 000000000..bd44447e7 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupViewModel.kt @@ -0,0 +1,696 @@ +package dev.minios.pdaiv1.presentation.screen.setup + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.model.Quadruple +import dev.minios.pdaiv1.core.common.model.Quintuple +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.validation.common.CommonStringValidator +import dev.minios.pdaiv1.core.validation.path.FilePathValidator +import dev.minios.pdaiv1.core.validation.url.UrlValidator +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.auth.AuthorizationCredentials +import dev.minios.pdaiv1.domain.interactor.settings.SetupConnectionInterActor +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActor +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.downloadable.DeleteModelUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.DownloadModelUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalQnnModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.ScanCustomModelsUseCase +import dev.minios.pdaiv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase +import dev.minios.pdaiv1.domain.usecase.settings.GetConfigurationUseCase +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.screen.setup.mappers.allowedModes +import dev.minios.pdaiv1.presentation.screen.setup.mappers.mapLocalCustomMediaPipeSwitchState +import dev.minios.pdaiv1.presentation.screen.setup.mappers.mapLocalCustomOnnxSwitchState +import dev.minios.pdaiv1.presentation.screen.setup.mappers.mapLocalCustomQnnSwitchState +import dev.minios.pdaiv1.presentation.screen.setup.mappers.mapToUi +import dev.minios.pdaiv1.presentation.utils.Constants +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.kotlin.subscribeBy + +class ServerSetupViewModel( + launchSource: LaunchSource, + dispatchersProvider: DispatchersProvider, + getConfigurationUseCase: GetConfigurationUseCase, + getLocalOnnxModelsUseCase: GetLocalOnnxModelsUseCase, + getLocalMediaPipeModelsUseCase: GetLocalMediaPipeModelsUseCase, + getLocalQnnModelsUseCase: GetLocalQnnModelsUseCase, + fetchAndGetHuggingFaceModelsUseCase: FetchAndGetHuggingFaceModelsUseCase, + falAiEndpointRepository: FalAiEndpointRepository, + private val urlValidator: UrlValidator, + private val stringValidator: CommonStringValidator, + private val filePathValidator: FilePathValidator, + private val setupConnectionInterActor: SetupConnectionInterActor, + private val downloadModelUseCase: DownloadModelUseCase, + private val deleteModelUseCase: DeleteModelUseCase, + private val scanCustomModelsUseCase: ScanCustomModelsUseCase, + private val schedulersProvider: SchedulersProvider, + private val preferenceManager: PreferenceManager, + private val wakeLockInterActor: WakeLockInterActor, + private val mainRouter: MainRouter, + private val buildInfoProvider: BuildInfoProvider, +) : MviRxViewModel() { + + override val initialState = ServerSetupState( + showBackNavArrow = launchSource == LaunchSource.SETTINGS, + ) + + override val effectDispatcher = dispatchersProvider.immediate + + private val credentials: AuthorizationCredentials + get() = when (currentState.mode) { + ServerSource.AUTOMATIC1111 -> { + if (!currentState.demoMode) currentState.credentialsDomain() + else AuthorizationCredentials.None + } + + ServerSource.SWARM_UI -> currentState.credentialsDomain() + + else -> AuthorizationCredentials.None + } + + private val downloadDisposables: MutableList> = mutableListOf() + + init { + !Single.zip( + getConfigurationUseCase(), + getLocalOnnxModelsUseCase(), + getLocalMediaPipeModelsUseCase(), + getLocalQnnModelsUseCase(), + fetchAndGetHuggingFaceModelsUseCase(), + ::Quintuple, + ) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { (configuration, onnxModels, mpModels, qnnModels, hfModels) -> + updateState { state -> + state.copy( + huggingFaceModels = hfModels.map(HuggingFaceModel::alias), + huggingFaceModel = configuration.huggingFaceModel, + huggingFaceApiKey = configuration.huggingFaceApiKey, + openAiApiKey = configuration.openAiApiKey, + stabilityAiApiKey = configuration.stabilityAiApiKey, + falAiApiKey = configuration.falAiApiKey, + localOnnxModels = onnxModels.mapToUi(), + localOnnxCustomModel = onnxModels.mapLocalCustomOnnxSwitchState(), + localOnnxCustomModelPath = configuration.localOnnxModelPath, + localMediaPipeModels = mpModels.mapToUi(), + localMediaPipeCustomModel = mpModels.mapLocalCustomMediaPipeSwitchState(), + localMediaPipeCustomModelPath = configuration.localMediaPipeModelPath, + localQnnModels = qnnModels.mapToUi(), + localQnnCustomModel = qnnModels.mapLocalCustomQnnSwitchState(), + localQnnCustomModelPath = configuration.localQnnModelPath, + mode = configuration.source, + allowedModes = buildInfoProvider.allowedModes, + demoMode = configuration.demoMode, + serverUrl = configuration.serverUrl, + swarmUiUrl = configuration.swarmUiUrl, + authType = configuration.authType, + ) + .withCredentials(configuration.authCredentials) + .withHordeApiKey(configuration.hordeApiKey) + } + } + + // Load FalAi endpoints + !falAiEndpointRepository.getAll() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { endpoints -> + val selectedId = preferenceManager.falAiSelectedEndpointId + val selected = endpoints.firstOrNull { it.endpointId == selectedId } + ?: endpoints.firstOrNull() + updateState { state -> + state.copy( + falAiEndpoints = endpoints.map { endpoint -> + ServerSetupState.FalAiEndpointUi( + id = endpoint.id, + endpointId = endpoint.endpointId, + title = endpoint.title, + category = endpoint.category.name, + isCustom = endpoint.isCustom, + ) + }, + falAiSelectedEndpoint = selected?.endpointId ?: "", + ) + } + } + } + + override fun onCleared() { + downloadDisposables.forEach { (_, disposable) -> + disposable.dispose() + } + super.onCleared() + } + + override fun processIntent(intent: ServerSetupIntent) = when (intent) { + is ServerSetupIntent.AllowLocalCustomModel -> { + updateState { state -> + state.withAllowCustomModel(intent.allow) + } + if (intent.allow) { + when (currentState.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> scanOnnxCustomModels() + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> scanMediaPipeCustomModels() + ServerSource.LOCAL_QUALCOMM_QNN -> scanQnnCustomModels() + else -> Unit + } + } else Unit + } + + ServerSetupIntent.DismissDialog -> setScreenModal(Modal.None) + + is ServerSetupIntent.LocalModel.ClickReduce -> localModelDownloadClickReducer(intent.model) + + is ServerSetupIntent.LocalModel.DeleteConfirm -> updateState { + !deleteModelUseCase(intent.model.id) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) + it.withDeletedLocalModel(intent.model) + } + + is ServerSetupIntent.SelectLocalModel -> updateState { state -> + state.withSelectedLocalModel(intent.model) + } + + ServerSetupIntent.MainButtonClick -> when (currentState.step) { + ServerSetupState.Step.SOURCE -> updateState { + it.copy(step = ServerSetupState.Step.CONFIGURE) + } + + ServerSetupState.Step.CONFIGURE -> validateAndConnectToServer() + } + + is ServerSetupIntent.UpdateAuthType -> updateState { + it.copy(authType = intent.type) + } + + is ServerSetupIntent.UpdateDemoMode -> updateState { + it.copy(demoMode = intent.value) + } + + is ServerSetupIntent.UpdateHordeApiKey -> updateState { + it.copy(hordeApiKey = intent.key, hordeApiKeyValidationError = null) + } + + is ServerSetupIntent.UpdateHordeDefaultApiKey -> updateState { + it.copy(hordeDefaultApiKey = intent.value) + } + + is ServerSetupIntent.UpdateHuggingFaceApiKey -> updateState { + it.copy(huggingFaceApiKey = intent.key) + } + + is ServerSetupIntent.UpdateHuggingFaceModel -> updateState { + it.copy(huggingFaceModel = intent.model) + } + + is ServerSetupIntent.UpdateLogin -> updateState { + it.copy(login = intent.login, loginValidationError = null) + } + + is ServerSetupIntent.UpdateOpenAiApiKey -> updateState { + it.copy(openAiApiKey = intent.key) + } + + is ServerSetupIntent.UpdatePassword -> updateState { + it.copy(password = intent.password, passwordValidationError = null) + } + + is ServerSetupIntent.UpdatePasswordVisibility -> updateState { + it.copy(passwordVisible = !intent.visible) + } + + is ServerSetupIntent.UpdateServerMode -> updateState { + it.copy(mode = intent.mode) + } + + is ServerSetupIntent.UpdateServerUrl -> updateState { + it.copy(serverUrl = intent.url, serverUrlValidationError = null) + } + + is ServerSetupIntent.UpdateSwarmUiUrl -> updateState { + it.copy(swarmUiUrl = intent.url, swarmUiUrlValidationError = null) + } + + is ServerSetupIntent.LaunchUrl -> { + emitEffect(ServerSetupEffect.LaunchUrl(intent.url)) + } + + ServerSetupIntent.LaunchManageStoragePermission -> { + emitEffect(ServerSetupEffect.LaunchManageStoragePermission) + } + + ServerSetupIntent.NavigateBack -> if (currentState.step == ServerSetupState.Step.entries.first()) { + mainRouter.navigateBack() + } else { + emitEffect(ServerSetupEffect.HideKeyboard) + updateState { + it.copy(step = ServerSetupState.Step.entries[it.step.ordinal - 1]) + } + } + + is ServerSetupIntent.UpdateStabilityAiApiKey -> updateState { + it.copy(stabilityAiApiKey = intent.key) + } + + is ServerSetupIntent.UpdateFalAiApiKey -> updateState { + it.copy(falAiApiKey = intent.key) + } + + is ServerSetupIntent.UpdateFalAiEndpoint -> updateState { + it.copy(falAiSelectedEndpoint = intent.endpoint.endpointId) + } + + ServerSetupIntent.ConnectToLocalHost -> connectToServer() + + is ServerSetupIntent.SelectLocalModelPath -> updateState { state -> + state.withLocalCustomModelPath(intent.value) + } + + is ServerSetupIntent.SelectLocalQnnModelPath -> { + updateState { state -> + state.copy(localQnnCustomModelPath = intent.value) + } + // Trigger scan after path update + preferenceManager.localQnnCustomModelPath = intent.value + scanQnnCustomModels() + } + + is ServerSetupIntent.ScanCustomModels -> when (currentState.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> scanOnnxCustomModels() + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> scanMediaPipeCustomModels() + ServerSource.LOCAL_QUALCOMM_QNN -> scanQnnCustomModels() + else -> Unit + } + + is ServerSetupIntent.LocalModel.DownloadConfirm -> with(intent) { + download(modelId, url) + } + } + + private fun validateAndConnectToServer() { + if (!validate()) return + connectToServer() + } + + private fun connectToServer() { + emitEffect(ServerSetupEffect.HideKeyboard) + !when (currentState.mode) { + ServerSource.HORDE -> connectToHorde() + ServerSource.LOCAL_MICROSOFT_ONNX -> connectToLocalDiffusion() + ServerSource.AUTOMATIC1111 -> connectToAutomaticInstance() + ServerSource.HUGGING_FACE -> connectToHuggingFace() + ServerSource.OPEN_AI -> connectToOpenAi() + ServerSource.STABILITY_AI -> connectToStabilityAi() + ServerSource.FAL_AI -> connectToFalAi() + ServerSource.SWARM_UI -> connectToSwarmUi() + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> connectToMediaPipe() + ServerSource.LOCAL_QUALCOMM_QNN -> connectToQnn() + } + .doOnSubscribe { setScreenModal(Modal.Communicating(canCancel = false)) } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { result -> + result.fold( + onSuccess = { onSetupComplete() }, + onFailure = { t -> + val message = t.localizedMessage ?: "Bad key" + setScreenModal(Modal.Error(message.asUiText())) + }, + ) + } + } + + private fun validate(): Boolean = when (currentState.mode) { + ServerSource.AUTOMATIC1111 -> { + if (currentState.demoMode) true + else validateServerUrlAndCredentials(currentState.serverUrl) + } + + ServerSource.SWARM_UI -> validateServerUrlAndCredentials(currentState.swarmUiUrl) + + ServerSource.HORDE -> { + if (currentState.hordeDefaultApiKey) true + else { + val validation = stringValidator(currentState.hordeApiKey) + updateState { + it.copy(hordeApiKeyValidationError = validation.mapToUi()) + } + validation.isValid + } + } + + ServerSource.LOCAL_MICROSOFT_ONNX -> if (currentState.localOnnxCustomModel) { + val validation = filePathValidator(currentState.localOnnxCustomModelPath) + updateState { + it.copy(localCustomOnnxPathValidationError = validation.mapToUi()) + } + validation.isValid && currentState.scannedOnnxCustomModels.isNotEmpty() + } else { + currentState.localOnnxModels.any { it.downloaded } + } + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> when { + buildInfoProvider.type == BuildType.FOSS -> false + currentState.localMediaPipeCustomModel -> { + val validation = filePathValidator(currentState.localMediaPipeCustomModelPath) + updateState { + it.copy(localCustomMediaPipePathValidationError = validation.mapToUi()) + } + validation.isValid && currentState.scannedMediaPipeCustomModels.isNotEmpty() + } + + else -> { + currentState.localMediaPipeModels.any { it.downloaded } + } + } + + ServerSource.HUGGING_FACE -> { + val validation = stringValidator(currentState.huggingFaceApiKey) + updateState { + it.copy(huggingFaceApiKeyValidationError = validation.mapToUi()) + } + validation.isValid + } + + ServerSource.OPEN_AI -> { + val validation = stringValidator(currentState.openAiApiKey) + updateState { + it.copy(openAiApiKeyValidationError = validation.mapToUi()) + } + validation.isValid + } + + ServerSource.STABILITY_AI -> { + val validation = stringValidator(currentState.stabilityAiApiKey) + updateState { + it.copy(stabilityAiApiKeyValidationError = validation.mapToUi()) + } + validation.isValid + } + + ServerSource.FAL_AI -> { + val validation = stringValidator(currentState.falAiApiKey) + updateState { + it.copy(falAiApiKeyValidationError = validation.mapToUi()) + } + validation.isValid + } + + ServerSource.LOCAL_QUALCOMM_QNN -> if (currentState.localQnnCustomModel) { + val validation = filePathValidator(currentState.localQnnCustomModelPath) + updateState { + it.copy(localCustomQnnPathValidationError = validation.mapToUi()) + } + validation.isValid && currentState.scannedQnnCustomModels.isNotEmpty() + } else { + currentState.localQnnModels.any { it.downloaded } + } + } + + private fun validateServerUrlAndCredentials(url: String): Boolean { + val serverUrlValidation = urlValidator(url) + var isValid = serverUrlValidation.isValid + updateState { state -> + var newState = state.copy( + serverUrlValidationError = if (state.mode == ServerSource.AUTOMATIC1111) { + serverUrlValidation.mapToUi() + } else { + state.serverUrlValidationError + }, + swarmUiUrlValidationError = if (state.mode == ServerSource.SWARM_UI) { + serverUrlValidation.mapToUi() + } else { + state.swarmUiUrlValidationError + }, + ) + if (currentState.authType == ServerSetupState.AuthType.HTTP_BASIC) { + val loginValidation = stringValidator(currentState.login) + val passwordValidation = stringValidator(currentState.password) + newState = newState.copy( + loginValidationError = loginValidation.mapToUi(), + passwordValidationError = passwordValidation.mapToUi() + ) + isValid = isValid && loginValidation.isValid && passwordValidation.isValid + } + if (serverUrlValidation.validationError is UrlValidator.Error.Localhost + && newState.loginValidationError == null + && newState.passwordValidationError == null + ) { + newState = newState.copy(screenModal = Modal.ConnectLocalHost) + } + newState + } + return isValid + } + + private fun connectToAutomaticInstance(): Single> { + val demoMode = currentState.demoMode + val connectUrl = if (demoMode) currentState.demoModeUrl else currentState.serverUrl + return setupConnectionInterActor.connectToA1111( + url = connectUrl, + isDemo = demoMode, + credentials = credentials, + ) + } + + private fun connectToSwarmUi() = setupConnectionInterActor.connectToSwarmUi( + url = currentState.swarmUiUrl, + credentials = credentials, + ) + + private fun connectToHuggingFace() = with(currentState) { + setupConnectionInterActor.connectToHuggingFace( + apiKey = huggingFaceApiKey, + model = huggingFaceModel, + ) + } + + private fun connectToOpenAi() = setupConnectionInterActor.connectToOpenAi( + apiKey = currentState.openAiApiKey, + ) + + private fun connectToStabilityAi() = setupConnectionInterActor.connectToStabilityAi( + apiKey = currentState.stabilityAiApiKey, + ) + + private fun connectToFalAi() = setupConnectionInterActor.connectToFalAi( + apiKey = currentState.falAiApiKey, + endpointId = currentState.falAiSelectedEndpoint, + ) + + private fun connectToHorde(): Single> { + val testApiKey = if (currentState.hordeDefaultApiKey) { + Constants.HORDE_DEFAULT_API_KEY + } else { + currentState.hordeApiKey + } + return setupConnectionInterActor.connectToHorde(testApiKey) + } + + private fun connectToLocalDiffusion(): Single> { + preferenceManager.localOnnxCustomModelPath = currentState.localOnnxCustomModelPath + // Use saved model if available, otherwise first available model + val availableModels = if (currentState.localOnnxCustomModel) { + currentState.scannedOnnxCustomModels + } else { + currentState.localOnnxModels.filter { it.downloaded } + } + val savedModelId = preferenceManager.localOnnxModelId + val modelToUse = availableModels.find { it.id == savedModelId } + ?: availableModels.firstOrNull() + val localModelId = modelToUse?.id ?: "" + return setupConnectionInterActor.connectToLocal(localModelId) + } + + private fun connectToMediaPipe(): Single> { + preferenceManager.localMediaPipeCustomModelPath = currentState.localMediaPipeCustomModelPath + // Use saved model if available, otherwise first available model + val availableModels = if (currentState.localMediaPipeCustomModel) { + currentState.scannedMediaPipeCustomModels + } else { + currentState.localMediaPipeModels.filter { it.downloaded } + } + val savedModelId = preferenceManager.localMediaPipeModelId + val modelToUse = availableModels.find { it.id == savedModelId } + ?: availableModels.firstOrNull() + val localModelId = modelToUse?.id ?: "" + return setupConnectionInterActor.connectToMediaPipe(localModelId) + } + + private fun connectToQnn(): Single> { + preferenceManager.localQnnCustomModelPath = currentState.localQnnCustomModelPath + // Use saved model if available, otherwise first available model + val availableModels = if (currentState.localQnnCustomModel) { + currentState.scannedQnnCustomModels + } else { + currentState.localQnnModels.filter { it.downloaded } + } + val savedModelId = preferenceManager.localQnnModelId + val modelToUse = availableModels.find { it.id == savedModelId } + ?: availableModels.firstOrNull() + val localModelId = modelToUse?.id ?: "" + val runOnCpu = modelToUse?.runOnCpu ?: false + return setupConnectionInterActor.connectToQnn(localModelId, runOnCpu) + } + + private fun localModelDownloadClickReducer(value: ServerSetupState.LocalModel) { + fun localModel(): ServerSetupState.LocalModel = + currentState.localModels.firstOrNull { it.id == value.id } + ?.let { value.copy(selected = it.selected) } + ?: value + + when { + // User cancels download + localModel().downloadState is DownloadState.Downloading -> { + val index = downloadDisposables.indexOfFirst { it.first == localModel().id } + if (index != -1) { + downloadDisposables[index].second.dispose() + downloadDisposables.removeAt(index) + } + !deleteModelUseCase(localModel().id) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) + updateState { state -> + state.withUpdatedLocalModel( + value = localModel().copy(downloadState = DownloadState.Unknown), + ) + } + } + // User deletes local model + localModel().downloaded -> updateState { + it.copy(screenModal = Modal.DeleteLocalModelConfirm(localModel())) + } + // User requested new download operation + else -> setScreenModal(Modal.SelectDownloadSource(localModel().id)) + } + } + + private fun download(modelId: String, url: String) { + val localModel = + currentState.localModels.firstOrNull { it.id == modelId } ?: return + + updateState { state -> + state.withUpdatedLocalModel( + localModel.copy(downloadState = DownloadState.Downloading()), + ) + } + !downloadModelUseCase(localModel.id, url) + .distinctUntilChanged() + .doOnSubscribe { wakeLockInterActor.acquireWakelockUseCase() } + .doFinally { wakeLockInterActor.releaseWakeLockUseCase() } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + errorLog(t) + val message = t.localizedMessage ?: "Error" + updateState { state -> + state.withUpdatedLocalModel( + localModel.copy( + downloadState = DownloadState.Error(t), + ), + ) + } + setScreenModal(Modal.Error(message.asUiText())) + }, + onNext = { downloadState -> + updateState { state -> + state.withUpdatedLocalModel( + localModel.copy( + downloadState = downloadState, + downloaded = downloadState is DownloadState.Complete + ), + ) + } + }, + ) + .also { downloadDisposables.add(localModel.id to it) } + } + + private fun setScreenModal(value: Modal) = updateState { + it.copy(screenModal = value) + } + + private fun onSetupComplete() { + preferenceManager.forceSetupAfterUpdate = false + processIntent(ServerSetupIntent.DismissDialog) + mainRouter.navigateToHomeScreen() + } + + private fun scanOnnxCustomModels() { + !scanCustomModelsUseCase(LocalAiModel.Type.ONNX) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { models -> + updateState { state -> + state.copy( + scannedOnnxCustomModels = models.map { model -> + ServerSetupState.LocalModel( + id = model.id, + name = model.name, + size = model.size, + downloaded = true, + selected = false, + downloadState = DownloadState.Unknown, + ) + } + ) + } + } + } + + private fun scanMediaPipeCustomModels() { + !scanCustomModelsUseCase(LocalAiModel.Type.MediaPipe) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { models -> + updateState { state -> + state.copy( + scannedMediaPipeCustomModels = models.map { model -> + ServerSetupState.LocalModel( + id = model.id, + name = model.name, + size = model.size, + downloaded = true, + selected = false, + downloadState = DownloadState.Unknown, + ) + } + ) + } + } + } + + private fun scanQnnCustomModels() { + !scanCustomModelsUseCase(LocalAiModel.Type.QNN) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { models -> + updateState { state -> + state.copy( + scannedQnnCustomModels = models.map { model -> + ServerSetupState.LocalModel( + id = model.id, + name = model.name, + size = model.size, + downloaded = true, + selected = false, + downloadState = DownloadState.Unknown, + ) + } + ) + } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/components/ConfigurationModeButton.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/components/ConfigurationModeButton.kt similarity index 87% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/components/ConfigurationModeButton.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/components/ConfigurationModeButton.kt index f8626a6e3..91ff1a7a8 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/components/ConfigurationModeButton.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/components/ConfigurationModeButton.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalLayoutApi::class) -package com.shifthackz.aisdv1.presentation.screen.setup.components +package dev.minios.pdaiv1.presentation.screen.setup.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -31,11 +31,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.screen.setup.mappers.mapToUi -import com.shifthackz.aisdv1.presentation.widget.source.getName -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.screen.setup.mappers.mapToUi +import dev.minios.pdaiv1.presentation.widget.source.getName +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ConfigurationModeButton( @@ -77,10 +77,12 @@ fun ConfigurationModeButton( ServerSource.HORDE, ServerSource.OPEN_AI, ServerSource.STABILITY_AI, + ServerSource.FAL_AI, ServerSource.HUGGING_FACE -> Icons.Default.Cloud ServerSource.LOCAL_MICROSOFT_ONNX, - ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> Icons.Default.Android + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE, + ServerSource.LOCAL_QUALCOMM_QNN -> Icons.Default.Android }, contentDescription = null, ) @@ -100,8 +102,10 @@ fun ConfigurationModeButton( ServerSource.OPEN_AI -> LocalizationR.string.hint_open_ai_sub_title ServerSource.LOCAL_MICROSOFT_ONNX -> LocalizationR.string.hint_local_diffusion_sub_title ServerSource.STABILITY_AI -> LocalizationR.string.hint_stability_ai_sub_title + ServerSource.FAL_AI -> LocalizationR.string.hint_fal_ai_sub_title ServerSource.SWARM_UI -> LocalizationR.string.hint_swarm_ui_sub_title ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> LocalizationR.string.hint_mediapipe_sub_title + ServerSource.LOCAL_QUALCOMM_QNN -> LocalizationR.string.hint_qnn_sub_title } descriptionId?.let { resId -> Text( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/components/ConfigurationStepBar.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/components/ConfigurationStepBar.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/components/ConfigurationStepBar.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/components/ConfigurationStepBar.kt index 8b1275928..2e348a19e 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/components/ConfigurationStepBar.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/components/ConfigurationStepBar.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.components +package dev.minios.pdaiv1.presentation.screen.setup.components import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -25,8 +25,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ConfigurationStepBar( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/AuthCredentialsForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/AuthCredentialsForm.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/AuthCredentialsForm.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/AuthCredentialsForm.kt index 8fd0ae780..c26e9bb6a 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/AuthCredentialsForm.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/AuthCredentialsForm.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.forms +package dev.minios.pdaiv1.presentation.screen.setup.forms import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.text.KeyboardOptions @@ -16,13 +16,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.widget.input.DropdownTextField -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.widget.input.DropdownTextField +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ColumnScope.AuthCredentialsForm( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/Automatic1111Form.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/Automatic1111Form.kt similarity index 89% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/Automatic1111Form.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/Automatic1111Form.kt index 42e70a9f5..798177aa3 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/Automatic1111Form.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/Automatic1111Form.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.forms +package dev.minios.pdaiv1.presentation.screen.setup.forms import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -16,13 +16,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun Automatic1111Form( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/FalAiForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/FalAiForm.kt new file mode 100644 index 000000000..93b7cf77a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/FalAiForm.kt @@ -0,0 +1,73 @@ +package dev.minios.pdaiv1.presentation.screen.setup.forms + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Help +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun FalAiForm( + modifier: Modifier = Modifier, + state: ServerSetupState, + processIntent: (ServerSetupIntent) -> Unit, +) { + Column( + modifier = modifier.padding(horizontal = 16.dp), + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = 32.dp, bottom = 8.dp), + text = stringResource(id = LocalizationR.string.hint_fal_ai_title), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + Text( + modifier = Modifier.padding(top = 16.dp), + text = stringResource(id = LocalizationR.string.hint_fal_ai_sub_title), + style = MaterialTheme.typography.bodyMedium, + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + value = state.falAiApiKey, + onValueChange = { + processIntent(ServerSetupIntent.UpdateFalAiApiKey(it)) + }, + label = { Text(stringResource(id = LocalizationR.string.hint_server_horde_api_key)) }, + isError = state.falAiApiKeyValidationError != null, + supportingText = { + state.falAiApiKeyValidationError + ?.let { Text(it.asString(), color = MaterialTheme.colorScheme.error) } + }, + colors = textFieldColors, + ) + SettingsItem( + modifier = Modifier + .padding(top = 16.dp) + .fillMaxWidth(), + startIcon = Icons.AutoMirrored.Filled.Help, + text = LocalizationR.string.hint_fal_ai_about.asUiText(), + onClick = { processIntent(ServerSetupIntent.LaunchUrl.FalAiInfo) }, + ) + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/HordeForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/HordeForm.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/HordeForm.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/HordeForm.kt index 0dea761e4..1deba8a4f 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/HordeForm.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/HordeForm.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.forms +package dev.minios.pdaiv1.presentation.screen.setup.forms import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -19,14 +19,14 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.utils.Constants -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun HordeForm( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/HuggingFaceForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/HuggingFaceForm.kt similarity index 87% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/HuggingFaceForm.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/HuggingFaceForm.kt index 20195c4e5..9255ed47a 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/HuggingFaceForm.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/HuggingFaceForm.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalFoundationApi::class) -package com.shifthackz.aisdv1.presentation.screen.setup.forms +package dev.minios.pdaiv1.presentation.screen.setup.forms import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column @@ -22,16 +22,16 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.widget.input.DropdownTextField -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.widget.input.DropdownTextField +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/LocalDiffusionForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/LocalDiffusionForm.kt new file mode 100644 index 000000000..79280e546 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/LocalDiffusionForm.kt @@ -0,0 +1,505 @@ +package dev.minios.pdaiv1.presentation.screen.setup.forms + +import android.content.Intent +import android.provider.DocumentsContract +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.outlined.FileDownload +import androidx.compose.material.icons.outlined.FileDownloadDone +import androidx.compose.material.icons.outlined.FileDownloadOff +import androidx.compose.material.icons.outlined.Landslide +import androidx.compose.material.icons.outlined.Memory +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.file.LOCAL_DIFFUSION_CUSTOM_PATH +import dev.minios.pdaiv1.core.extensions.getRealPath +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupScreenTags.CUSTOM_MODEL_SWITCH +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun LocalDiffusionForm( + modifier: Modifier = Modifier, + state: ServerSetupState, + buildInfoProvider: BuildInfoProvider = BuildInfoProvider.stub, + processIntent: (ServerSetupIntent) -> Unit = {}, +) { + val isQnn = state.mode == ServerSource.LOCAL_QUALCOMM_QNN + val modelItemUi: @Composable (ServerSetupState.LocalModel) -> Unit = { model -> + Column( + modifier = Modifier + .padding(vertical = 8.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(color = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.8f)) + .defaultMinSize(minHeight = 50.dp) + .border( + width = 2.dp, + shape = RoundedCornerShape(16.dp), + color = if (!isQnn && model.selected) MaterialTheme.colorScheme.primary else Color.Transparent, + ) + .let { mod -> + if (isQnn) mod else mod.clickable { processIntent(ServerSetupIntent.SelectLocalModel(model)) } + } + .padding(horizontal = 12.dp, vertical = 8.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + val icon = when { + model.id == LocalAiModel.CustomOnnx.id -> Icons.Outlined.Landslide + model.id == LocalAiModel.CustomMediaPipe.id -> Icons.Outlined.Landslide + else -> Icons.Outlined.Memory + } + Icon( + modifier = Modifier + .padding(end = 8.dp) + .size(42.dp), + imageVector = icon, + contentDescription = null, + ) + Column( + modifier = Modifier.weight(1f), + ) { + Text( + text = model.name, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + if (model.id != LocalAiModel.CustomOnnx.id + && model.id != LocalAiModel.CustomMediaPipe.id + ) { + Text( + text = model.size, + style = MaterialTheme.typography.bodySmall, + ) + } + } + // Status icon on right side (for non-custom models) + if (model.id != LocalAiModel.CustomOnnx.id + && model.id != LocalAiModel.CustomMediaPipe.id + ) { + Icon( + modifier = Modifier + .padding(start = 8.dp) + .size(24.dp), + imageVector = when { + model.downloaded -> Icons.Outlined.FileDownloadDone + model.downloadState is DownloadState.Downloading -> Icons.Outlined.FileDownload + else -> Icons.Outlined.FileDownloadOff + }, + contentDescription = null, + tint = when { + model.downloaded -> MaterialTheme.colorScheme.primary + else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + }, + ) + } + } + + // Custom model path info + if (model.id == LocalAiModel.CustomOnnx.id + || model.id == LocalAiModel.CustomMediaPipe.id + ) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(id = LocalizationR.string.model_local_custom_title), + style = MaterialTheme.typography.bodyMedium, + ) + if (model.id == LocalAiModel.CustomOnnx.id) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(id = LocalizationR.string.model_local_custom_sub_title), + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(modifier = Modifier.height(4.dp)) + + fun folderModifier(treeNum: Int) = + Modifier.padding(start = (treeNum - 1) * 12.dp) + + val folderStyle = MaterialTheme.typography.bodySmall + Text( + modifier = Modifier.padding(start = 12.dp), + text = state.localOnnxCustomModelPath, + style = folderStyle, + ) + + Text(modifier = folderModifier(3), text = "text_encoder", style = folderStyle) + Text(modifier = folderModifier(4), text = "model.ort", style = folderStyle) + Text(modifier = folderModifier(3), text = "tokenizer", style = folderStyle) + Text(modifier = folderModifier(4), text = "merges.txt", style = folderStyle) + Text(modifier = folderModifier(3), text = "special_tokens_map.json", style = folderStyle) + Text(modifier = folderModifier(4), text = "tokenizer_config.json", style = folderStyle) + Text(modifier = folderModifier(4), text = "vocab.json", style = folderStyle) + Text(modifier = folderModifier(3), text = "unet", style = folderStyle) + Text(modifier = folderModifier(4), text = "model.ort", style = folderStyle) + Text(modifier = folderModifier(3), text = "vae_decoder", style = folderStyle) + Text(modifier = folderModifier(4), text = "model.ort", style = folderStyle) + } + } + + // Progress indicator for downloading + when (val downloadState = model.downloadState) { + is DownloadState.Downloading -> { + Spacer(modifier = Modifier.height(8.dp)) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = { downloadState.percent / 100f }, + ) + Text( + modifier = Modifier.padding(top = 4.dp), + text = "${downloadState.percent}%", + style = MaterialTheme.typography.bodySmall, + ) + } + is DownloadState.Error -> { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(id = LocalizationR.string.error_download_fail), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error, + ) + } + else -> Unit + } + + // Action buttons (for non-custom models) + if (model.id != LocalAiModel.CustomOnnx.id + && model.id != LocalAiModel.CustomMediaPipe.id + ) { + // Cancel button during download + if (model.downloadState is DownloadState.Downloading) { + Spacer(modifier = Modifier.height(8.dp)) + Row { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = { + processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.cancel), + color = MaterialTheme.colorScheme.error, + ) + } + } + } + + // Retry button on error + if (model.downloadState is DownloadState.Error) { + Spacer(modifier = Modifier.height(8.dp)) + Row { + Button( + modifier = Modifier.weight(1f), + onClick = { + processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.retry), + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + } + + // Download button for not downloaded models + if (!model.downloaded + && model.downloadState !is DownloadState.Downloading + && model.downloadState !is DownloadState.Error + ) { + Spacer(modifier = Modifier.height(8.dp)) + Row { + Button( + modifier = Modifier.weight(1f), + onClick = { + processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.download), + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + } + + // Delete button for downloaded and selected models + if (model.downloaded && model.selected) { + Spacer(modifier = Modifier.height(8.dp)) + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.delete), + color = MaterialTheme.colorScheme.error, + ) + } + } + } + } + } + + Column( + modifier = modifier.padding(horizontal = 16.dp), + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = 32.dp, bottom = 8.dp), + text = stringResource( + id = when (state.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX, + ServerSource.LOCAL_QUALCOMM_QNN -> LocalizationR.string.hint_local_diffusion_title + else -> LocalizationR.string.hint_mediapipe_title + }, + ), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + Text( + modifier = Modifier.padding(top = 16.dp, bottom = 16.dp), + text = stringResource( + id = when (state.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> LocalizationR.string.hint_local_diffusion_sub_title + ServerSource.LOCAL_QUALCOMM_QNN -> LocalizationR.string.hint_qnn_sub_title + else -> LocalizationR.string.hint_mediapipe_sub_title + }, + ), + style = MaterialTheme.typography.bodyMedium, + ) + if (buildInfoProvider.type != BuildType.PLAY) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Switch( + modifier = Modifier.testTag(CUSTOM_MODEL_SWITCH), + checked = state.localCustomModel, + onCheckedChange = { + processIntent(ServerSetupIntent.AllowLocalCustomModel(it)) + }, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_custom_switch), + ) + } + } + if (state.localCustomModel && buildInfoProvider.type != BuildType.PLAY) { + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_permission_header), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + Text( + modifier = Modifier.padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_permission_title), + style = MaterialTheme.typography.bodyMedium, + ) + OutlinedButton( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 8.dp), + onClick = { processIntent(ServerSetupIntent.LaunchManageStoragePermission) }, + ) { + Text( + text = stringResource(id = LocalizationR.string.model_local_permission_button), + color = LocalContentColor.current, + ) + } + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_path_header), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + val context = LocalContext.current + val uriFlags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + result.data?.data?.let { uri -> + context.contentResolver.takePersistableUriPermission(uri, uriFlags) + val docUri = DocumentsContract.buildDocumentUriUsingTree( + uri, + DocumentsContract.getTreeDocumentId(uri) + ) + getRealPath(context, docUri) + ?.let(ServerSetupIntent::SelectLocalModelPath) + ?.let(processIntent::invoke) + } + } + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 14.dp), + value = state.localCustomModelPath, + onValueChange = { string -> + string.filter { it != '\n' } + .let(ServerSetupIntent::SelectLocalModelPath) + .let(processIntent::invoke) + }, + enabled = true, + label = { Text(stringResource(LocalizationR.string.model_local_path_title)) }, + trailingIcon = { + IconButton( + onClick = { + processIntent( + ServerSetupIntent.SelectLocalModelPath(LOCAL_DIFFUSION_CUSTOM_PATH) + ) + }, + content = { + Icon( + imageVector = Icons.Default.Refresh, + contentDescription = "Reset", + ) + }, + ) + }, + isError = state.localCustomModelPathValidationError != null, + supportingText = { + state.localCustomModelPathValidationError + ?.let { Text(it.asString(), color = MaterialTheme.colorScheme.error) } + }, + colors = textFieldColors, + ) + OutlinedButton( + modifier = Modifier + .fillMaxSize() + .padding(top = 4.dp, bottom = 8.dp), + onClick = { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { + addFlags(uriFlags) + } + launcher.launch(intent) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.model_local_path_button), + color = LocalContentColor.current, + ) + } + + // Scan button + OutlinedButton( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp, bottom = 8.dp), + onClick = { processIntent(ServerSetupIntent.ScanCustomModels) }, + ) { + Icon( + modifier = Modifier.padding(end = 8.dp), + imageVector = Icons.Default.Refresh, + contentDescription = null, + ) + Text( + text = stringResource(id = LocalizationR.string.action_scan_models), + color = LocalContentColor.current, + ) + } + Spacer(modifier = Modifier.height(8.dp)) + } + + // Show scanned custom models when custom model mode is enabled + if (state.localCustomModel && buildInfoProvider.type != BuildType.PLAY) { + val scannedModels = when (state.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> state.scannedOnnxCustomModels + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> state.scannedMediaPipeCustomModels + else -> emptyList() + } + if (scannedModels.isEmpty()) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + text = stringResource(id = LocalizationR.string.model_scan_empty), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + } else { + Text( + modifier = Modifier.padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_scan_found, scannedModels.size), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + ) + scannedModels.forEach { localModel -> + modelItemUi(localModel) + } + } + } + + // Show standard models when not in custom mode + if (!state.localCustomModel) { + state.localModels + .filter { + it.id != LocalAiModel.CustomOnnx.id && it.id != LocalAiModel.CustomMediaPipe.id + } + .forEach { localModel -> modelItemUi(localModel) } + } + Text( + modifier = Modifier.padding(top = 16.dp), + text = stringResource(id = LocalizationR.string.hint_local_diffusion_warning), + style = MaterialTheme.typography.bodyMedium, + ) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/MediaPipeForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/MediaPipeForm.kt new file mode 100644 index 000000000..622d3e12b --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/MediaPipeForm.kt @@ -0,0 +1,29 @@ +package dev.minios.pdaiv1.presentation.screen.setup.forms + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState + +@Composable +fun MediaPipeForm( + modifier: Modifier = Modifier, + state: ServerSetupState, + buildInfoProvider: BuildInfoProvider = BuildInfoProvider.stub, + processIntent: (ServerSetupIntent) -> Unit = {}, +) { + when (buildInfoProvider.type) { + BuildType.FOSS -> { + + } + + else -> LocalDiffusionForm( + modifier = modifier, + state = state, + buildInfoProvider = buildInfoProvider, + processIntent = processIntent, + ) + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/OpenAiForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/OpenAiForm.kt similarity index 87% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/OpenAiForm.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/OpenAiForm.kt index d6f95c2da..02db6ac47 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/OpenAiForm.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/OpenAiForm.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalFoundationApi::class) -package com.shifthackz.aisdv1.presentation.screen.setup.forms +package dev.minios.pdaiv1.presentation.screen.setup.forms import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column @@ -22,15 +22,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/QnnForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/QnnForm.kt new file mode 100644 index 000000000..591435e9d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/QnnForm.kt @@ -0,0 +1,445 @@ +package dev.minios.pdaiv1.presentation.screen.setup.forms + +import android.content.Intent +import android.provider.DocumentsContract +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.outlined.FileDownload +import androidx.compose.material.icons.outlined.FileDownloadDone +import androidx.compose.material.icons.outlined.FileDownloadOff +import androidx.compose.material.icons.outlined.Landslide +import androidx.compose.material.icons.outlined.Memory +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.file.LOCAL_DIFFUSION_CUSTOM_PATH +import dev.minios.pdaiv1.core.extensions.getRealPath +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun QnnForm( + modifier: Modifier = Modifier, + state: ServerSetupState, + buildInfoProvider: BuildInfoProvider = BuildInfoProvider.stub, + processIntent: (ServerSetupIntent) -> Unit = {}, +) { + val modelItemUi: @Composable (ServerSetupState.LocalModel) -> Unit = { model -> + val isCustomModel = model.id == LocalAiModel.CustomQnn.id + Column( + modifier = Modifier + .padding(vertical = 8.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(color = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.8f)) + .defaultMinSize(minHeight = 50.dp) + .border( + width = 2.dp, + color = if (model.selected) MaterialTheme.colorScheme.primary + else Color.Transparent, + shape = RoundedCornerShape(16.dp), + ) + .clickable { + processIntent(ServerSetupIntent.SelectLocalModel(model)) + } + .padding(horizontal = 12.dp, vertical = 8.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier + .padding(end = 8.dp) + .size(42.dp), + imageVector = if (isCustomModel) Icons.Outlined.Landslide else Icons.Outlined.Memory, + contentDescription = null, + ) + Column( + modifier = Modifier.weight(1f), + ) { + Text( + text = model.name, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (!isCustomModel) { + Text( + text = model.size, + style = MaterialTheme.typography.bodySmall, + ) + } + } + if (!isCustomModel) { + Icon( + modifier = Modifier + .padding(start = 8.dp) + .size(24.dp), + imageVector = when { + model.downloaded -> Icons.Outlined.FileDownloadDone + model.downloadState is DownloadState.Downloading -> Icons.Outlined.FileDownload + else -> Icons.Outlined.FileDownloadOff + }, + contentDescription = null, + tint = when { + model.downloaded -> MaterialTheme.colorScheme.primary + else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + }, + ) + } + } + + // Custom model info + if (isCustomModel) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(id = LocalizationR.string.model_local_custom_title), + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(id = LocalizationR.string.model_qnn_custom_sub_title), + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(modifier = Modifier.height(4.dp)) + + fun folderModifier(treeNum: Int) = + Modifier.padding(start = ((treeNum - 1) * 12).dp) + + val folderStyle = MaterialTheme.typography.bodySmall + Text( + modifier = Modifier.padding(start = 12.dp), + text = state.localQnnCustomModelPath, + style = folderStyle, + ) + + Text(modifier = folderModifier(3), text = "clip.bin / clip.mnn", style = folderStyle) + Text(modifier = folderModifier(3), text = "unet.bin / unet.mnn", style = folderStyle) + Text(modifier = folderModifier(3), text = "vae_decoder.bin / vae_decoder.mnn", style = folderStyle) + Text(modifier = folderModifier(3), text = "tokenizer.json", style = folderStyle) + } + + // Progress for downloading + if (!isCustomModel) { + when (val downloadState = model.downloadState) { + is DownloadState.Downloading -> { + Spacer(modifier = Modifier.height(8.dp)) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = { downloadState.percent / 100f }, + ) + Text( + modifier = Modifier.padding(top = 4.dp), + text = "${downloadState.percent}%", + style = MaterialTheme.typography.bodySmall, + ) + } + else -> Unit + } + + if (model.downloadState is DownloadState.Downloading) { + Spacer(modifier = Modifier.height(8.dp)) + Row { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = { + processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.cancel), + color = MaterialTheme.colorScheme.error, + ) + } + } + } + if (!model.downloaded && model.downloadState !is DownloadState.Downloading) { + Spacer(modifier = Modifier.height(8.dp)) + Row { + Button( + modifier = Modifier.weight(1f), + onClick = { + processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.download), + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + } + if (model.downloaded && model.selected) { + Spacer(modifier = Modifier.height(8.dp)) + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + processIntent(ServerSetupIntent.LocalModel.ClickReduce(model)) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.delete), + color = MaterialTheme.colorScheme.error, + ) + } + } + } + } + } + + Column( + modifier = modifier.padding(horizontal = 16.dp), + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = 32.dp, bottom = 8.dp), + text = stringResource(id = LocalizationR.string.hint_qnn_title), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + Text( + modifier = Modifier.padding(top = 16.dp, bottom = 16.dp), + text = stringResource(id = LocalizationR.string.hint_qnn_sub_title), + style = MaterialTheme.typography.bodyMedium, + ) + + // Custom model switch + if (buildInfoProvider.type != BuildType.PLAY) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Switch( + checked = state.localQnnCustomModel, + onCheckedChange = { + processIntent(ServerSetupIntent.AllowLocalCustomModel(it)) + }, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_custom_switch), + ) + } + } + + // Custom model path configuration + if (state.localQnnCustomModel && buildInfoProvider.type != BuildType.PLAY) { + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_permission_header), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + Text( + modifier = Modifier.padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_permission_title), + style = MaterialTheme.typography.bodyMedium, + ) + OutlinedButton( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 8.dp), + onClick = { processIntent(ServerSetupIntent.LaunchManageStoragePermission) }, + ) { + Text( + text = stringResource(id = LocalizationR.string.model_local_permission_button), + color = LocalContentColor.current, + ) + } + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_local_path_header), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + val context = LocalContext.current + val uriFlags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + result.data?.data?.let { uri -> + context.contentResolver.takePersistableUriPermission(uri, uriFlags) + val docUri = DocumentsContract.buildDocumentUriUsingTree( + uri, + DocumentsContract.getTreeDocumentId(uri) + ) + getRealPath(context, docUri) + ?.let(ServerSetupIntent::SelectLocalQnnModelPath) + ?.let(processIntent::invoke) + } + } + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 14.dp), + value = state.localQnnCustomModelPath, + onValueChange = { string -> + string.filter { it != '\n' } + .let(ServerSetupIntent::SelectLocalQnnModelPath) + .let(processIntent::invoke) + }, + enabled = true, + label = { Text(stringResource(LocalizationR.string.model_local_path_title)) }, + trailingIcon = { + IconButton( + onClick = { + processIntent( + ServerSetupIntent.SelectLocalQnnModelPath(LOCAL_DIFFUSION_CUSTOM_PATH) + ) + }, + content = { + Icon( + imageVector = Icons.Default.Refresh, + contentDescription = "Reset", + ) + }, + ) + }, + isError = state.localCustomQnnPathValidationError != null, + supportingText = { + state.localCustomQnnPathValidationError + ?.let { Text(it.asString(), color = MaterialTheme.colorScheme.error) } + }, + colors = textFieldColors, + ) + OutlinedButton( + modifier = Modifier + .fillMaxSize() + .padding(top = 4.dp, bottom = 8.dp), + onClick = { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { + addFlags(uriFlags) + } + launcher.launch(intent) + }, + ) { + Text( + text = stringResource(id = LocalizationR.string.model_local_path_button), + color = LocalContentColor.current, + ) + } + + // Scan button + OutlinedButton( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp, bottom = 8.dp), + onClick = { processIntent(ServerSetupIntent.ScanCustomModels) }, + ) { + Icon( + modifier = Modifier.padding(end = 8.dp), + imageVector = Icons.Default.Refresh, + contentDescription = null, + ) + Text( + text = stringResource(id = LocalizationR.string.action_scan_models), + color = LocalContentColor.current, + ) + } + Spacer(modifier = Modifier.height(8.dp)) + } + + // Show scanned custom models when custom model mode is enabled + if (state.localQnnCustomModel && buildInfoProvider.type != BuildType.PLAY) { + if (state.scannedQnnCustomModels.isEmpty()) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + text = stringResource(id = LocalizationR.string.model_scan_empty), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + } else { + Text( + modifier = Modifier.padding(vertical = 8.dp), + text = stringResource(id = LocalizationR.string.model_scan_found, state.scannedQnnCustomModels.size), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + ) + state.scannedQnnCustomModels.forEach { localModel -> + modelItemUi(localModel) + } + } + } + + // Filter models - show only downloaded models when not in custom mode + if (!state.localQnnCustomModel) { + val modelsToShow = state.localQnnModels.filter { model -> + model.id != LocalAiModel.CustomQnn.id + } + + if (modelsToShow.isEmpty()) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + text = "No QNN models available yet.\n\nQNN models will be available for download from HuggingFace (xororz/sd-qnn).", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + } else { + modelsToShow.forEach { localModel -> + modelItemUi(localModel) + } + } + } + + Text( + modifier = Modifier.padding(top = 16.dp), + text = stringResource(id = LocalizationR.string.hint_local_diffusion_warning), + style = MaterialTheme.typography.bodyMedium, + ) + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/StabilityAiForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/StabilityAiForm.kt similarity index 83% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/StabilityAiForm.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/StabilityAiForm.kt index 4bf059c71..505164410 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/StabilityAiForm.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/StabilityAiForm.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.forms +package dev.minios.pdaiv1.presentation.screen.setup.forms import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -14,13 +14,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun StabilityAiForm( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/SwarmUiForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/SwarmUiForm.kt similarity index 85% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/SwarmUiForm.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/SwarmUiForm.kt index b562c19b1..fd2f69ada 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/SwarmUiForm.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/forms/SwarmUiForm.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.forms +package dev.minios.pdaiv1.presentation.screen.setup.forms import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -14,13 +14,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.theme.textFieldColors -import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.widget.item.SettingsItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun SwarmUiForm( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/FeatureTagMapper.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/FeatureTagMapper.kt similarity index 82% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/FeatureTagMapper.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/FeatureTagMapper.kt index e5a55208d..fae9acdfc 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/FeatureTagMapper.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/FeatureTagMapper.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.mappers +package dev.minios.pdaiv1.presentation.screen.setup.mappers import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import com.shifthackz.aisdv1.domain.entity.FeatureTag -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.domain.entity.FeatureTag +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun FeatureTag.mapToUi(): String { diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/LocalModelMappers.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/LocalModelMappers.kt new file mode 100644 index 000000000..728328d6d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/LocalModelMappers.kt @@ -0,0 +1,38 @@ +package dev.minios.pdaiv1.presentation.screen.setup.mappers + +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState + +fun List.mapToUi(): List = map(LocalAiModel::mapToUi) + +fun List.mapLocalCustomOnnxSwitchState(): Boolean = + find { it.selected && it.id == LocalAiModel.CustomOnnx.id } != null + +fun List.mapLocalCustomMediaPipeSwitchState(): Boolean = + find { it.selected && it.id == LocalAiModel.CustomMediaPipe.id } != null + +fun List.mapLocalCustomQnnSwitchState(): Boolean = + find { it.selected && it.id == LocalAiModel.CustomQnn.id } != null + +fun LocalAiModel.mapToUi(): ServerSetupState.LocalModel = with(this) { + ServerSetupState.LocalModel( + id = id, + name = name, + size = size, + downloaded = downloaded, + selected = selected, + runOnCpu = runOnCpu, + ) +} + +fun List.withNewState( + model: ServerSetupState.LocalModel?, +): List = + map { + if (model == null) return@map it + if (it.id == model.id) model + else { + if (model.selected) it.copy(selected = false) + else it + } + } diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ModesMapper.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ModesMapper.kt new file mode 100644 index 000000000..401e66508 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ModesMapper.kt @@ -0,0 +1,9 @@ +package dev.minios.pdaiv1.presentation.screen.setup.mappers + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.domain.entity.ServerSource + +val BuildInfoProvider.allowedModes: List + get() = ServerSource + .entries + .filter { it.allowedInBuilds.contains(type) } diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationFilePathErrorMapper.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationFilePathErrorMapper.kt new file mode 100644 index 000000000..70cd36ee9 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationFilePathErrorMapper.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.presentation.screen.setup.mappers + +import dev.minios.pdaiv1.core.localization.R +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.path.FilePathValidator + +fun ValidationResult.mapToUi(): UiText? { + if (this.isValid) return null + return when (validationError as FilePathValidator.Error) { + FilePathValidator.Error.Empty -> R.string.error_empty_field + FilePathValidator.Error.Invalid -> R.string.error_invalid + }.asUiText() +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationStringErrorMapper.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationStringErrorMapper.kt new file mode 100644 index 000000000..10ed8cbbc --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationStringErrorMapper.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.presentation.screen.setup.mappers + +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.common.CommonStringValidator +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +fun ValidationResult.mapToUi(): UiText? { + if (this.isValid) return null + return when (validationError as CommonStringValidator.Error) { + CommonStringValidator.Error.Empty -> LocalizationR.string.error_empty_field + }.asUiText() +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationUrlMapper.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationUrlMapper.kt new file mode 100644 index 000000000..c523f7779 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/mappers/ServerSetupValidationUrlMapper.kt @@ -0,0 +1,18 @@ +package dev.minios.pdaiv1.presentation.screen.setup.mappers + +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.url.UrlValidator +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +fun ValidationResult.mapToUi(): UiText? { + if (this.isValid) return null + return when (validationError as UrlValidator.Error) { + UrlValidator.Error.BadScheme -> LocalizationR.string.error_invalid_scheme + UrlValidator.Error.BadPort -> LocalizationR.string.error_invalid_port + UrlValidator.Error.Empty -> LocalizationR.string.error_empty_url + UrlValidator.Error.Invalid -> LocalizationR.string.error_invalid_url + UrlValidator.Error.Localhost -> null + }?.asUiText() +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/BaseStepWrapper.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/BaseStepWrapper.kt similarity index 91% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/BaseStepWrapper.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/BaseStepWrapper.kt index 415a6e80b..eb3bad461 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/BaseStepWrapper.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/BaseStepWrapper.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.steps +package dev.minios.pdaiv1.presentation.screen.setup.steps import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/ConfigurationStep.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/ConfigurationStep.kt new file mode 100644 index 000000000..fc9b47ce3 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/ConfigurationStep.kt @@ -0,0 +1,83 @@ +package dev.minios.pdaiv1.presentation.screen.setup.steps + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.screen.setup.forms.Automatic1111Form +import dev.minios.pdaiv1.presentation.screen.setup.forms.FalAiForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.HordeForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.HuggingFaceForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.LocalDiffusionForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.MediaPipeForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.OpenAiForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.QnnForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.StabilityAiForm +import dev.minios.pdaiv1.presentation.screen.setup.forms.SwarmUiForm + +@Composable +fun ConfigurationStep( + modifier: Modifier = Modifier, + state: ServerSetupState, + buildInfoProvider: BuildInfoProvider = BuildInfoProvider.stub, + processIntent: (ServerSetupIntent) -> Unit = {}, +) { + BaseServerSetupStateWrapper(modifier) { + when (state.mode) { + ServerSource.AUTOMATIC1111 -> Automatic1111Form( + state = state, + processIntent = processIntent, + ) + + ServerSource.HORDE -> HordeForm( + state = state, + processIntent = processIntent, + ) + + ServerSource.LOCAL_MICROSOFT_ONNX -> LocalDiffusionForm( + state = state, + buildInfoProvider = buildInfoProvider, + processIntent = processIntent, + ) + + ServerSource.HUGGING_FACE -> HuggingFaceForm( + state = state, + processIntent = processIntent, + ) + + ServerSource.OPEN_AI -> OpenAiForm( + state = state, + processIntent = processIntent, + ) + + ServerSource.STABILITY_AI -> StabilityAiForm( + state = state, + processIntent = processIntent, + ) + + ServerSource.FAL_AI -> FalAiForm( + state = state, + processIntent = processIntent, + ) + + ServerSource.SWARM_UI -> SwarmUiForm( + state = state, + processIntent = processIntent, + ) + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> MediaPipeForm( + state = state, + buildInfoProvider = buildInfoProvider, + processIntent = processIntent, + ) + + ServerSource.LOCAL_QUALCOMM_QNN -> QnnForm( + state = state, + buildInfoProvider = buildInfoProvider, + processIntent = processIntent, + ) + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/SourceSelectionStep.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/SourceSelectionStep.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/SourceSelectionStep.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/SourceSelectionStep.kt index f77794584..0266b6d73 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/steps/SourceSelectionStep.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/setup/steps/SourceSelectionStep.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.setup.steps +package dev.minios.pdaiv1.presentation.screen.setup.steps import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -17,10 +17,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupIntent -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState -import com.shifthackz.aisdv1.presentation.screen.setup.components.ConfigurationModeButton +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupIntent +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState +import dev.minios.pdaiv1.presentation.screen.setup.components.ConfigurationModeButton import kotlin.math.abs @Composable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/splash/SplashScreen.kt similarity index 91% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/splash/SplashScreen.kt index e1424eeba..8eb3eed59 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/splash/SplashScreen.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.splash +package dev.minios.pdaiv1.presentation.screen.splash import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/splash/SplashViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/splash/SplashViewModel.kt new file mode 100644 index 000000000..bba61405c --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/splash/SplashViewModel.kt @@ -0,0 +1,32 @@ +package dev.minios.pdaiv1.presentation.screen.splash + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.usecase.splash.SplashNavigationUseCase +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.postSplashNavigation +import com.shifthackz.android.core.mvi.EmptyEffect +import com.shifthackz.android.core.mvi.EmptyIntent +import com.shifthackz.android.core.mvi.EmptyState +import io.reactivex.rxjava3.kotlin.subscribeBy + +class SplashViewModel( + mainRouter: MainRouter, + splashNavigationUseCase: SplashNavigationUseCase, + dispatchersProvider: DispatchersProvider, + schedulersProvider: SchedulersProvider, +) : MviRxViewModel() { + + override val initialState = EmptyState + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !splashNavigationUseCase() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { action -> mainRouter.postSplashNavigation(action) } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageScreen.kt new file mode 100755 index 000000000..7e8ac12b3 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageScreen.kt @@ -0,0 +1,196 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package dev.minios.pdaiv1.presentation.screen.txt2img + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AutoFixNormal +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.Button +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.shifthackz.android.core.mvi.MviComponent +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.modal.ModalRenderer +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.widget.input.GenerationInputForm +import dev.minios.pdaiv1.presentation.widget.scaffold.CollapsibleScaffold +import dev.minios.pdaiv1.presentation.widget.toolbar.GenerationBottomToolbar +import dev.minios.pdaiv1.presentation.widget.work.BackgroundWorkWidget +import org.koin.androidx.compose.koinViewModel +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun TextToImageScreen() { + MviComponent( + viewModel = koinViewModel(), + ) { state, intentHandler -> + TextToImageScreenContent( + modifier = Modifier.fillMaxSize(), + state = state, + processIntent = intentHandler, + ) + } +} + +@Composable +fun TextToImageScreenContent( + modifier: Modifier = Modifier, + state: TextToImageState, + scrollState: ScrollState = rememberScrollState(), + processIntent: (GenerationMviIntent) -> Unit = {}, +) { + val promptChipTextFieldState = remember { mutableStateOf(TextFieldValue()) } + val negativePromptChipTextFieldState = remember { mutableStateOf(TextFieldValue()) } + val keyboardController = LocalSoftwareKeyboardController.current + Box(modifier) { + CollapsibleScaffold( + scrollState = scrollState, + bottomToolbarHeight = 150.dp, + bottomNavBarHeight = if (state.onBoardingDemo) 0.dp else 80.dp, + topBarContent = { + CenterAlignedTopAppBar( + navigationIcon = { + IconButton(onClick = { + processIntent(GenerationMviIntent.Drawer(DrawerIntent.Open)) + }) { + Icon( + imageVector = Icons.Default.Menu, + contentDescription = "Menu", + ) + } + }, + title = { + Text( + text = stringResource(id = LocalizationR.string.title_text_to_image), + style = MaterialTheme.typography.headlineMedium, + ) + }, + actions = { + IconButton(onClick = { + processIntent( + GenerationMviIntent.SetModal( + Modal.PromptBottomSheet( + AiGenerationResult.Type.TEXT_TO_IMAGE, + ), + ), + ) + }) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = null, + ) + } + }, + windowInsets = WindowInsets(0, 0, 0, 0), + ) + }, + bottomBar = { + GenerationBottomToolbar( + strokeAccentState = !state.hasValidationErrors, + state = state, + processIntent = processIntent, + ) { + Button( + modifier = Modifier + .height(height = 60.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 16.dp), + onClick = { + keyboardController?.hide() + promptChipTextFieldState.value.text.takeIf(String::isNotBlank) + ?.let { "${state.prompt}, ${it.trim()}" } + ?.let(GenerationMviIntent.Update::Prompt) + ?.let(processIntent::invoke) + ?.also { promptChipTextFieldState.value = TextFieldValue("") } + + negativePromptChipTextFieldState.value.text.takeIf(String::isNotBlank) + ?.let { "${state.negativePrompt}, ${it.trim()}" } + ?.let(GenerationMviIntent.Update::NegativePrompt) + ?.let(processIntent::invoke) + ?.also { + negativePromptChipTextFieldState.value = TextFieldValue("") + } + + processIntent(GenerationMviIntent.Generate) + }, + enabled = !state.hasValidationErrors + ) { + Icon( + modifier = Modifier.size(18.dp), + imageVector = Icons.Default.AutoFixNormal, + contentDescription = "Imagine", + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(id = LocalizationR.string.action_generate), + color = LocalContentColor.current, + ) + } + } + }, + contentScrollable = { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + GenerationInputForm( + state = state, + promptChipTextFieldState = promptChipTextFieldState, + negativePromptChipTextFieldState = negativePromptChipTextFieldState, + processIntent = processIntent, + ) + } + } + ) + ModalRenderer( + screenModal = state.screenModal, + processIntent = { (it as? GenerationMviIntent)?.let(processIntent::invoke) }, + ) + } +} + +//region PREVIEWS +@Composable +@Preview(showSystemUi = true, showBackground = true) +fun PreviewStateContent() { + TextToImageScreenContent( + modifier = Modifier.fillMaxSize(), + state = TextToImageState( + prompt = "Opel Astra H OPC", + negativePrompt = "White background", + samplingSteps = 55, + availableSamplers = listOf("Euler a") + ) + ) +} +//endregion diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageState.kt new file mode 100644 index 000000000..91274351e --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageState.kt @@ -0,0 +1,219 @@ +package dev.minios.pdaiv1.presentation.screen.txt2img + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator +import dev.minios.pdaiv1.domain.entity.ADetailerConfig +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.domain.entity.HiresConfig +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.QnnHiresConfig +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.OpenAiQuality +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.OpenAiStyle +import dev.minios.pdaiv1.domain.entity.Scheduler +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.StabilityAiClipGuidance +import dev.minios.pdaiv1.domain.entity.StabilityAiStylePreset +import dev.minios.pdaiv1.domain.entity.TextToImagePayload +import dev.minios.pdaiv1.presentation.core.GenerationMviState +import dev.minios.pdaiv1.presentation.model.FalAiEndpointUi +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Immutable +data class TextToImageState( + override val onBoardingDemo: Boolean = false, + override val screenModal: Modal = Modal.None, + override val mode: ServerSource = ServerSource.AUTOMATIC1111, + override val modelType: ModelType = ModelType.SD_1_5, + override val advancedToggleButtonVisible: Boolean = true, + override val advancedOptionsVisible: Boolean = false, + override val formPromptTaggedInput: Boolean = false, + override val prompt: String = "", + override val negativePrompt: String = "", + override val width: String = 512.toString(), + override val height: String = 512.toString(), + override val samplingSteps: Int = 20, + override val cfgScale: Float = 7f, + override val distilledCfgScale: Float = 3.5f, + override val restoreFaces: Boolean = false, + override val seed: String = "", + override val subSeed: String = "", + override val subSeedStrength: Float = 0f, + override val selectedSampler: String = "", + override val selectedScheduler: Scheduler = Scheduler.AUTOMATIC, + override val availableForgeModules: List = emptyList(), + override val selectedForgeModules: List = emptyList(), + override val aDetailerConfig: ADetailerConfig = ADetailerConfig.DISABLED, + override val hiresConfig: HiresConfig = HiresConfig.DISABLED, + override val selectedStylePreset: StabilityAiStylePreset = StabilityAiStylePreset.NONE, + override val selectedClipGuidancePreset: StabilityAiClipGuidance = StabilityAiClipGuidance.NONE, + override val openAiModel: OpenAiModel = OpenAiModel.DALL_E_2, + override val openAiSize: OpenAiSize = OpenAiSize.W1024_H1024, + override val openAiQuality: OpenAiQuality = OpenAiQuality.STANDARD, + override val openAiStyle: OpenAiStyle = OpenAiStyle.VIVID, + override val availableSamplers: List = emptyList(), + override val widthValidationError: UiText? = null, + override val heightValidationError: UiText? = null, + override val nsfw: Boolean = false, + override val batchCount: Int = 1, + override val generateButtonEnabled: Boolean = true, + override val falAiEndpoints: List = emptyList(), + override val falAiSelectedEndpoint: FalAiEndpointUi? = null, + override val falAiPropertyValues: Map = emptyMap(), + override val falAiAdvancedVisible: Boolean = false, + override val qnnRunOnCpu: Boolean = false, + override val qnnHiresConfig: QnnHiresConfig = QnnHiresConfig.DISABLED, + override val modelName: String = "", +) : GenerationMviState() { + + override fun copyState( + onBoardingDemo: Boolean, + screenModal: Modal, + mode: ServerSource, + modelType: ModelType, + advancedToggleButtonVisible: Boolean, + advancedOptionsVisible: Boolean, + formPromptTaggedInput: Boolean, + prompt: String, + negativePrompt: String, + width: String, + height: String, + samplingSteps: Int, + cfgScale: Float, + distilledCfgScale: Float, + restoreFaces: Boolean, + seed: String, + subSeed: String, + subSeedStrength: Float, + selectedSampler: String, + availableSamplers: List, + selectedScheduler: Scheduler, + availableForgeModules: List, + selectedForgeModules: List, + aDetailerConfig: ADetailerConfig, + hiresConfig: HiresConfig, + selectedStylePreset: StabilityAiStylePreset, + selectedClipGuidancePreset: StabilityAiClipGuidance, + openAiModel: OpenAiModel, + openAiSize: OpenAiSize, + openAiQuality: OpenAiQuality, + openAiStyle: OpenAiStyle, + widthValidationError: UiText?, + heightValidationError: UiText?, + nsfw: Boolean, + batchCount: Int, + generateButtonEnabled: Boolean, + falAiEndpoints: List, + falAiSelectedEndpoint: FalAiEndpointUi?, + falAiPropertyValues: Map, + falAiAdvancedVisible: Boolean, + qnnRunOnCpu: Boolean, + qnnHiresConfig: QnnHiresConfig, + modelName: String, + ): GenerationMviState = copy( + onBoardingDemo = onBoardingDemo, + screenModal = screenModal, + mode = mode, + modelType = modelType, + advancedToggleButtonVisible = advancedToggleButtonVisible, + advancedOptionsVisible = advancedOptionsVisible, + formPromptTaggedInput = formPromptTaggedInput, + prompt = prompt, + negativePrompt = negativePrompt, + width = width, + height = height, + samplingSteps = samplingSteps, + cfgScale = cfgScale, + distilledCfgScale = distilledCfgScale, + restoreFaces = restoreFaces, + seed = seed, + subSeed = subSeed, + subSeedStrength = subSeedStrength, + selectedSampler = selectedSampler, + availableSamplers = availableSamplers, + selectedScheduler = selectedScheduler, + availableForgeModules = availableForgeModules, + selectedForgeModules = selectedForgeModules, + aDetailerConfig = aDetailerConfig, + hiresConfig = hiresConfig, + selectedStylePreset = selectedStylePreset, + selectedClipGuidancePreset = selectedClipGuidancePreset, + openAiModel = openAiModel, + openAiSize = openAiSize, + openAiQuality = openAiQuality, + openAiStyle = openAiStyle, + widthValidationError = widthValidationError, + heightValidationError = heightValidationError, + nsfw = nsfw, + batchCount = batchCount, + generateButtonEnabled = generateButtonEnabled, + falAiEndpoints = falAiEndpoints, + falAiSelectedEndpoint = falAiSelectedEndpoint, + falAiPropertyValues = falAiPropertyValues, + falAiAdvancedVisible = falAiAdvancedVisible, + qnnRunOnCpu = qnnRunOnCpu, + qnnHiresConfig = qnnHiresConfig, + modelName = modelName, + ) +} + +fun TextToImageState.mapToPayload(): TextToImagePayload = with(this) { + TextToImagePayload( + prompt = prompt.trim(), + negativePrompt = negativePrompt.trim(), + samplingSteps = samplingSteps, + cfgScale = cfgScale, + distilledCfgScale = distilledCfgScale, + width = when (mode) { + ServerSource.OPEN_AI -> openAiSize.width + else -> width.toIntOrNull() ?: 64 + }, + height = when (mode) { + ServerSource.OPEN_AI -> openAiSize.height + else -> height.toIntOrNull() ?: 64 + }, + restoreFaces = restoreFaces, + seed = seed.trim(), + subSeed = subSeed.trim(), + subSeedStrength = subSeedStrength, + sampler = selectedSampler, + scheduler = selectedScheduler, + nsfw = if (mode == ServerSource.HORDE) nsfw else false, + batchCount = if (mode == ServerSource.LOCAL_MICROSOFT_ONNX) 1 else batchCount, + style = openAiStyle.key.takeIf { + mode == ServerSource.OPEN_AI && openAiModel == OpenAiModel.DALL_E_3 + }, + quality = openAiQuality.key.takeIf { + mode == ServerSource.OPEN_AI && openAiModel == OpenAiModel.DALL_E_3 + }, + openAiModel = openAiModel.takeIf { mode == ServerSource.OPEN_AI }, + stabilityAiClipGuidance = selectedClipGuidancePreset.takeIf { mode == ServerSource.STABILITY_AI }, + stabilityAiStylePreset = selectedStylePreset.takeIf { mode == ServerSource.STABILITY_AI }, + aDetailer = aDetailerConfig.takeIf { mode == ServerSource.AUTOMATIC1111 } ?: ADetailerConfig.DISABLED, + hires = hiresConfig.takeIf { mode == ServerSource.AUTOMATIC1111 } ?: HiresConfig.DISABLED, + qnnHires = qnnHiresConfig.takeIf { mode == ServerSource.LOCAL_QUALCOMM_QNN && !qnnRunOnCpu } ?: QnnHiresConfig.DISABLED, + forgeModules = selectedForgeModules.takeIf { mode == ServerSource.AUTOMATIC1111 } ?: emptyList(), + modelName = modelName, + ) +} + +fun ValidationResult.mapToUi(): UiText? { + if (this.isValid) return null + return when (validationError as DimensionValidator.Error) { + DimensionValidator.Error.Empty -> LocalizationR.string.error_empty.asUiText() + is DimensionValidator.Error.LessThanMinimum -> UiText.Resource( + LocalizationR.string.error_min_size, + (validationError as DimensionValidator.Error.LessThanMinimum).min, + ) + is DimensionValidator.Error.BiggerThanMaximum -> UiText.Resource( + LocalizationR.string.error_max_size, + (validationError as DimensionValidator.Error.BiggerThanMaximum).max, + ) + else -> LocalizationR.string.error_invalid.asUiText() + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageViewModel.kt new file mode 100755 index 000000000..55128ae94 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageViewModel.kt @@ -0,0 +1,159 @@ +package dev.minios.pdaiv1.presentation.screen.txt2img + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildType +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.feature.diffusion.LocalDiffusion +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActor +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.caching.SaveLastResultToCacheUseCase +import dev.minios.pdaiv1.domain.usecase.forgemodule.GetForgeModulesUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.SaveGenerationResultUseCase +import dev.minios.pdaiv1.domain.usecase.generation.TextToImageUseCase +import dev.minios.pdaiv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.core.GenerationMviViewModel +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import com.shifthackz.android.core.mvi.EmptyEffect +import io.reactivex.rxjava3.kotlin.subscribeBy +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +class TextToImageViewModel( + dispatchersProvider: DispatchersProvider, + generationFormUpdateEvent: GenerationFormUpdateEvent, + getStableDiffusionSamplersUseCase: GetStableDiffusionSamplersUseCase, + getForgeModulesUseCase: GetForgeModulesUseCase, + observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + saveLastResultToCacheUseCase: SaveLastResultToCacheUseCase, + saveGenerationResultUseCase: SaveGenerationResultUseCase, + interruptGenerationUseCase: InterruptGenerationUseCase, + mainRouter: MainRouter, + drawerRouter: DrawerRouter, + dimensionValidator: DimensionValidator, + private val textToImageUseCase: TextToImageUseCase, + private val schedulersProvider: SchedulersProvider, + private val preferenceManager: PreferenceManager, + private val notificationManager: PushNotificationManager, + private val wakeLockInterActor: WakeLockInterActor, + private val backgroundTaskManager: BackgroundTaskManager, + private val backgroundWorkObserver: BackgroundWorkObserver, + private val buildInfoProvider: BuildInfoProvider, +) : GenerationMviViewModel( + preferenceManager = preferenceManager, + getStableDiffusionSamplersUseCase = getStableDiffusionSamplersUseCase, + getForgeModulesUseCase = getForgeModulesUseCase, + observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, + observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + saveLastResultToCacheUseCase = saveLastResultToCacheUseCase, + saveGenerationResultUseCase = saveGenerationResultUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, + mainRouter = mainRouter, + drawerRouter = drawerRouter, + dimensionValidator = dimensionValidator, + schedulersProvider = schedulersProvider, + backgroundWorkObserver = backgroundWorkObserver, +) { + + private val progressModal: Modal + get() = when (currentState.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX, + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> { + Modal.Generating(canCancel = preferenceManager.localOnnxAllowCancel) + } + ServerSource.LOCAL_QUALCOMM_QNN -> { + Modal.Generating(canCancel = true) + } + + else -> Modal.Communicating() + } + + override val initialState = TextToImageState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !generationFormUpdateEvent + .observeTxt2ImgForm() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onNext = { payload -> + (payload as? GenerationFormUpdateEvent.Payload.T2IForm) + ?.let(::updateFormPreviousAiGeneration) + ?.also { generationFormUpdateEvent.clear() } + }, + ) + } + + override fun generateDisposable() = currentState + .mapToPayload() + .let(textToImageUseCase::invoke) + .doOnSubscribe { + wakeLockInterActor.acquireWakelockUseCase() + setActiveModal(progressModal) + } + .doFinally { wakeLockInterActor.releaseWakeLockUseCase() } + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = { t -> + notificationManager.createAndShowInstant( + LocalizationR.string.notification_fail_title.asUiText(), + LocalizationR.string.notification_fail_sub_title.asUiText(), + ) + setActiveModal( + Modal.Error( + (t.localizedMessage ?: "Something went wrong").asUiText() + ) + ) + errorLog(t) + }, + onSuccess = { ai -> + notificationManager.createAndShowInstant( + LocalizationR.string.notification_finish_title.asUiText(), + LocalizationR.string.notification_finish_sub_title.asUiText(), + ) + setActiveModal( + Modal.Image.create( + list = ai, + autoSaveEnabled = preferenceManager.autoSaveAiResults, + reportEnabled = buildInfoProvider.type == BuildType.PLAY, + ) + ) + }, + ) + + override fun generateBackground() { + val payload = currentState.mapToPayload() + backgroundTaskManager.scheduleTextToImageTask(payload) + } + + override fun onReceivedHordeStatus(status: HordeProcessStatus) { + (currentState.screenModal as? Modal.Communicating) + ?.copy(hordeProcessStatus = status) + ?.let(::setActiveModal) + } + + override fun onReceivedLocalDiffusionStatus(status: LocalDiffusionStatus) { + (currentState.screenModal as? Modal.Generating) + ?.copy(status = status) + ?.let(::setActiveModal) + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/PdaiWebViewClient.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/PdaiWebViewClient.kt new file mode 100644 index 000000000..e323e8a14 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/PdaiWebViewClient.kt @@ -0,0 +1,30 @@ +package dev.minios.pdaiv1.presentation.screen.web + +import android.graphics.Bitmap +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient + +class PdaiWebViewClient( + private val onLoadingChanged: (Boolean) -> Unit = {}, +) : WebViewClient() { + + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + return false + } + + @Deprecated("Deprecated in Java", ReplaceWith("false")) + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + return false + } + + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + onLoadingChanged(true) + super.onPageStarted(view, url, favicon) + } + + override fun onPageFinished(view: WebView?, url: String?) { + onLoadingChanged(false) + super.onPageFinished(view, url) + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/WebViewScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/WebViewScreen.kt similarity index 90% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/WebViewScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/WebViewScreen.kt index c45e312d6..52efddf9a 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/WebViewScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/WebViewScreen.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.screen.web +package dev.minios.pdaiv1.presentation.screen.web import android.annotation.SuppressLint import android.view.ViewGroup @@ -28,7 +28,7 @@ fun WebViewScreen( domStorageEnabled = true javaScriptEnabled = true setSupportZoom(true) - webViewClient = client ?: SdaiWebViewClient() + webViewClient = client ?: PdaiWebViewClient() } loadUrl(url) webViewCallback(this) diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiIntent.kt new file mode 100644 index 000000000..7586e35ab --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiIntent.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.presentation.screen.web.webui + +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface WebUiIntent : MviIntent { + data object NavigateBack : WebUiIntent +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiScreen.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiScreen.kt similarity index 93% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiScreen.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiScreen.kt index 3d289807c..442f50075 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/web/webui/WebUiScreen.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.screen.web.webui +package dev.minios.pdaiv1.presentation.screen.web.webui import android.webkit.WebView import android.webkit.WebViewClient @@ -36,10 +36,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.screen.web.SdaiWebViewClient -import com.shifthackz.aisdv1.presentation.screen.web.WebViewScreen -import com.shifthackz.aisdv1.presentation.widget.source.getName +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.screen.web.PdaiWebViewClient +import dev.minios.pdaiv1.presentation.screen.web.WebViewScreen +import dev.minios.pdaiv1.presentation.widget.source.getName import org.koin.androidx.compose.koinViewModel @Composable @@ -59,7 +59,7 @@ private fun WebUiScreenContent( var pageLoading by remember { mutableStateOf(true) } var webView: WebView? by remember { mutableStateOf(null) } val client: WebViewClient = remember { - SdaiWebViewClient( + PdaiWebViewClient( onLoadingChanged = { pageLoading = it }, ) } diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiState.kt new file mode 100644 index 000000000..c2b00e403 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiState.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.presentation.screen.web.webui + +import dev.minios.pdaiv1.domain.entity.ServerSource +import com.shifthackz.android.core.mvi.MviState + +data class WebUiState( + val loading: Boolean = true, + val source: ServerSource = ServerSource.AUTOMATIC1111, + val url: String = "", +) : MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiViewModel.kt new file mode 100644 index 000000000..1f7a32b6a --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/screen/web/webui/WebUiViewModel.kt @@ -0,0 +1,39 @@ +package dev.minios.pdaiv1.presentation.screen.web.webui + +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import com.shifthackz.android.core.mvi.EmptyEffect + +class WebUiViewModel( + dispatchersProvider: DispatchersProvider, + private val mainRouter: MainRouter, + private val preferenceManager: PreferenceManager, +) : MviRxViewModel() { + + override val initialState: WebUiState = WebUiState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + updateState { state -> + state.copy( + loading = false, + source = preferenceManager.source, + url = when (preferenceManager.source) { + ServerSource.AUTOMATIC1111 -> preferenceManager.automatic1111ServerUrl + ServerSource.SWARM_UI -> preferenceManager.swarmUiServerUrl + else -> "" + } + ) + } + } + + override fun processIntent(intent: WebUiIntent) { + when (intent) { + WebUiIntent.NavigateBack -> mainRouter.navigateBack() + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/ColorTheme.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/ColorTheme.kt similarity index 92% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/ColorTheme.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/ColorTheme.kt index 4c91fbd63..381ef54f0 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/ColorTheme.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/ColorTheme.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.presentation.theme +package dev.minios.pdaiv1.presentation.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.preference.PreferenceManager import com.shifthackz.catppuccin.compose.CatppuccinMaterial import com.shifthackz.catppuccin.palette.Catppuccin import com.shifthackz.catppuccin.palette.CatppuccinPalette diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/SliderTheme.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/SliderTheme.kt similarity index 87% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/SliderTheme.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/SliderTheme.kt index 99a17d1e6..954b28cb4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/SliderTheme.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/SliderTheme.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.theme +package dev.minios.pdaiv1.presentation.theme import androidx.compose.material3.SliderColors import androidx.compose.material3.SliderDefaults diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/TextFieldColors.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/TextFieldColors.kt similarity index 92% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/TextFieldColors.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/TextFieldColors.kt index 1e7660055..13bfe6d14 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/TextFieldColors.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/TextFieldColors.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.theme +package dev.minios.pdaiv1.presentation.theme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextFieldColors diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppTheme.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppTheme.kt similarity index 92% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppTheme.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppTheme.kt index ed1f5940c..a61bbdf39 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/theme/global/AiSdAppTheme.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppTheme.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.theme.global +package dev.minios.pdaiv1.presentation.theme.global import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme @@ -8,7 +8,7 @@ import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import com.shifthackz.android.core.mvi.MviComponent -import com.shifthackz.aisdv1.presentation.theme.colorTokenPalette +import dev.minios.pdaiv1.presentation.theme.colorTokenPalette import com.shifthackz.catppuccin.compose.CatppuccinTheme import org.koin.androidx.compose.koinViewModel diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppThemeState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppThemeState.kt new file mode 100644 index 000000000..96fae6356 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppThemeState.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.presentation.theme.global + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class AiSdAppThemeState( + val stateKey: Long = System.currentTimeMillis(), + val systemColorPalette: Boolean = false, + val systemDarkTheme: Boolean = true, + val darkTheme: Boolean = true, + val colorToken: ColorToken = ColorToken.MAUVE, + val darkThemeToken: DarkThemeToken = DarkThemeToken.FRAPPE, +) : MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppThemeViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppThemeViewModel.kt new file mode 100644 index 000000000..7dc07e762 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/theme/global/AiSdAppThemeViewModel.kt @@ -0,0 +1,45 @@ +package dev.minios.pdaiv1.presentation.theme.global + +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import com.shifthackz.android.core.mvi.EmptyEffect +import com.shifthackz.android.core.mvi.EmptyIntent +import io.reactivex.rxjava3.kotlin.subscribeBy + +class AiSdAppThemeViewModel( + preferenceManager: PreferenceManager, + dispatchersProvider: DispatchersProvider, + schedulersProvider: SchedulersProvider, + timeProvider: TimeProvider, +) : MviRxViewModel() { + + override val initialState = AiSdAppThemeState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !preferenceManager + .observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda) { settings -> + updateState { state -> + state.copy( + stateKey = timeProvider.currentTimeMillis(), + systemColorPalette = settings.designUseSystemColorPalette, + systemDarkTheme = settings.designUseSystemDarkTheme, + darkTheme = settings.designDarkTheme, + colorToken = ColorToken.parse(settings.designColorToken), + darkThemeToken = DarkThemeToken.parse(settings.designDarkThemeToken), + ) + } + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/Constants.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/Constants.kt new file mode 100644 index 000000000..9164df749 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/Constants.kt @@ -0,0 +1,55 @@ +package dev.minios.pdaiv1.presentation.utils + +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute.HomeNavigation + +object Constants { + const val PAGINATION_PAYLOAD_SIZE = 30 // Smaller pages = faster initial load + const val DEBUG_MENU_ACCESS_TAPS = 7 + + const val SUB_SEED_STRENGTH_MIN = 0f + const val SUB_SEED_STRENGTH_MAX = 1f + + const val SAMPLING_STEPS_RANGE_MIN = 1 + const val SAMPLING_STEPS_RANGE_MAX = 150 + const val SAMPLING_STEPS_RANGE_STABILITY_AI_MAX = 50 + const val SAMPLING_STEPS_LOCAL_DIFFUSION_MAX = 50 + + const val BATCH_RANGE_MIN = 1 + const val BATCH_RANGE_MAX = 20 + + const val CFG_SCALE_RANGE_MIN = 1 + const val CFG_SCALE_RANGE_MAX = 35 + + const val DENOISING_STRENGTH_MIN = 0f + const val DENOISING_STRENGTH_MAX = 1f + + const val DRAW_CAP_RANGE_MIN = 1 + const val DRAW_CAP_RANGE_MAX = 60 + + const val MASK_BLUR_MIN = 1 + const val MASK_BLUR_MAX = 64 + + const val ONLY_MASKED_PADDING_MIN = 0 + const val ONLY_MASKED_PADDING_MAX = 256 + + const val EXTRA_MINIMUM = -10.0 + const val EXTRA_MAXIMUM = 10.0 + const val EXTRA_STEP = 0.25 + + const val MIME_TYPE_ZIP = "application/zip" + const val MIME_TYPE_JPG = "image/jpeg" + + const val HORDE_DEFAULT_API_KEY = "0000000000" + + val sizes = listOf("64", "128", "256", "320", "384", "448", "512") + + val homeRoutes = listOf( + HomeNavigation.TxtToImg, + HomeNavigation.ImgToImg, + HomeNavigation.FalAi, + HomeNavigation.Gallery, + HomeNavigation.Settings, + ) + + fun lora(alias: String) = "" +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/ExtrasFormatter.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/ExtrasFormatter.kt similarity index 98% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/ExtrasFormatter.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/ExtrasFormatter.kt index d36b7ac67..046e572fa 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/ExtrasFormatter.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/ExtrasFormatter.kt @@ -1,8 +1,8 @@ @file:Suppress("unused") -package com.shifthackz.aisdv1.presentation.utils +package dev.minios.pdaiv1.presentation.utils -import com.shifthackz.aisdv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.model.ExtraType object ExtrasFormatter { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/FileSavableExporter.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/FileSavableExporter.kt similarity index 82% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/FileSavableExporter.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/FileSavableExporter.kt index f131720c7..a4391b4f5 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/FileSavableExporter.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/FileSavableExporter.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.presentation.utils +package dev.minios.pdaiv1.presentation.utils import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.core.common.file.writeBitmap -import com.shifthackz.aisdv1.core.common.file.writeFilesToZip +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.file.writeBitmap +import dev.minios.pdaiv1.core.common.file.writeFilesToZip import io.reactivex.rxjava3.core.Single import java.io.File diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/PermissionUtil.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/PermissionUtil.kt similarity index 97% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/PermissionUtil.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/PermissionUtil.kt index 9e0b83edf..ff059f2e1 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/PermissionUtil.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/PermissionUtil.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.utils +package dev.minios.pdaiv1.presentation.utils import android.Manifest import android.content.Context diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/ReportProblemEmailComposer.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/ReportProblemEmailComposer.kt new file mode 100644 index 000000000..d9c329799 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/ReportProblemEmailComposer.kt @@ -0,0 +1,31 @@ +package dev.minios.pdaiv1.presentation.utils + +import android.content.Context +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.core.common.log.FileLoggingTree +import dev.minios.pdaiv1.core.sharing.shareEmail +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.io.File + +class ReportProblemEmailComposer : KoinComponent { + + private val fileProviderDescriptor: FileProviderDescriptor by inject() + private val buildInfoProvider: BuildInfoProvider by inject() + + fun invoke(context: Context) { + val logFile = File( + fileProviderDescriptor.logsCacheDirPath + + "/" + + FileLoggingTree.LOGGER_FILENAME + ) + context.shareEmail( + email = "crims0n@minios.dev", + subject = "PDAI - Problem report", + body = "PDAI : $buildInfoProvider", + file = if (!logFile.exists()) null else logFile, + fileProviderPath = fileProviderDescriptor.providerPath, + ) + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/UriToBitmap.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/UriToBitmap.kt similarity index 79% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/UriToBitmap.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/UriToBitmap.kt index 56930b013..fc1789ed5 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/UriToBitmap.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/utils/UriToBitmap.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.presentation.utils +package dev.minios.pdaiv1.presentation.utils import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri -import com.shifthackz.aisdv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.log.errorLog fun uriToBitmap(context: Context, uri: Uri): Bitmap? = try { val inputStream = context.contentResolver.openInputStream(uri) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/AccentColorSelector.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/AccentColorSelector.kt similarity index 97% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/AccentColorSelector.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/AccentColorSelector.kt index 6c6c77260..456ddac70 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/AccentColorSelector.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/AccentColorSelector.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.color +package dev.minios.pdaiv1.presentation.widget.color import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -11,7 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.ColorToken import com.shifthackz.catppuccin.palette.Catppuccin import com.shifthackz.catppuccin.palette.CatppuccinPalette diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/ColorComposable.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/ColorComposable.kt similarity index 97% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/ColorComposable.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/ColorComposable.kt index 9d7384bc4..1b1a08272 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/ColorComposable.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/ColorComposable.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.color +package dev.minios.pdaiv1.presentation.widget.color import androidx.compose.foundation.background import androidx.compose.foundation.clickable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/DarkThemeColorSelector.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/DarkThemeColorSelector.kt similarity index 90% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/DarkThemeColorSelector.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/DarkThemeColorSelector.kt index ee8c3145d..24b494473 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/color/DarkThemeColorSelector.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/color/DarkThemeColorSelector.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.color +package dev.minios.pdaiv1.presentation.widget.color import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -12,9 +12,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.domain.entity.ColorToken -import com.shifthackz.aisdv1.domain.entity.DarkThemeToken -import com.shifthackz.aisdv1.presentation.theme.toColor +import dev.minios.pdaiv1.domain.entity.ColorToken +import dev.minios.pdaiv1.domain.entity.DarkThemeToken +import dev.minios.pdaiv1.presentation.theme.toColor import com.shifthackz.catppuccin.palette.Catppuccin @Composable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityComposable.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityComposable.kt similarity index 95% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityComposable.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityComposable.kt index 28987940a..885ba4cfb 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityComposable.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityComposable.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.connectivity +package dev.minios.pdaiv1.presentation.widget.connectivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.LinearEasing @@ -25,12 +25,12 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.extensions.measureTextWidth -import com.shifthackz.aisdv1.presentation.theme.colors +import dev.minios.pdaiv1.core.extensions.measureTextWidth +import dev.minios.pdaiv1.presentation.theme.colors import com.shifthackz.android.core.mvi.MviComponent import com.shifthackz.catppuccin.palette.Catppuccin import org.koin.androidx.compose.koinViewModel -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ConnectivityComposable() { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityState.kt similarity index 90% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityState.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityState.kt index 9893b38fd..f4faa8cbb 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/connectivity/ConnectivityState.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityState.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.connectivity +package dev.minios.pdaiv1.presentation.widget.connectivity import com.shifthackz.android.core.mvi.MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityViewModel.kt new file mode 100644 index 000000000..4a490fcd2 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/connectivity/ConnectivityViewModel.kt @@ -0,0 +1,38 @@ +package dev.minios.pdaiv1.presentation.widget.connectivity + +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.connectivity.ObserveSeverConnectivityUseCase +import com.shifthackz.android.core.mvi.EmptyEffect +import com.shifthackz.android.core.mvi.EmptyIntent +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.kotlin.subscribeBy + +class ConnectivityViewModel( + preferenceManager: PreferenceManager, + observeServerConnectivityUseCase: ObserveSeverConnectivityUseCase, + dispatchersProvider: DispatchersProvider, + schedulersProvider: SchedulersProvider, +) : MviRxViewModel() { + + override val initialState = ConnectivityState.Uninitialized(preferenceManager.monitorConnectivity) + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !Flowable.combineLatest( + observeServerConnectivityUseCase(), + preferenceManager.observe().map(Settings::monitorConnectivity), + ::Pair, + ) + .map(ConnectivityState::consume) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog, EmptyLambda, ::emitState) + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/DecisionInteractiveDialog.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/DecisionInteractiveDialog.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/DecisionInteractiveDialog.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/DecisionInteractiveDialog.kt index 8c1d3248e..8c830fe0d 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/DecisionInteractiveDialog.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/DecisionInteractiveDialog.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.dialog +package dev.minios.pdaiv1.presentation.widget.dialog import androidx.annotation.StringRes import androidx.compose.foundation.shape.RoundedCornerShape @@ -11,9 +11,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun DecisionInteractiveDialog( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/ErrorDialog.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/ErrorDialog.kt similarity index 85% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/ErrorDialog.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/ErrorDialog.kt index 0cb700084..38dff7b26 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/ErrorDialog.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/ErrorDialog.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.dialog +package dev.minios.pdaiv1.presentation.widget.dialog import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.AlertDialog @@ -10,9 +10,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ErrorDialog( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/GenerationImageResultDialog.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/GenerationImageResultDialog.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/GenerationImageResultDialog.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/GenerationImageResultDialog.kt index e23155824..fda4bb596 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/GenerationImageResultDialog.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/GenerationImageResultDialog.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.dialog +package dev.minios.pdaiv1.presentation.widget.dialog import androidx.compose.foundation.Image import androidx.compose.foundation.clickable @@ -35,11 +35,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog -import com.shifthackz.aisdv1.core.imageprocessing.utils.base64ToBitmap -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryGridItemUi -import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryUiItem -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.imageprocessing.utils.base64ToBitmap +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryGridItemUi +import dev.minios.pdaiv1.presentation.screen.gallery.list.GalleryUiItem +import dev.minios.pdaiv1.core.localization.R as LocalizationR private const val MOCK_BASE_64 = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=" @@ -106,7 +106,7 @@ fun GenerationImageResultDialog( ) } } - + OutlinedButton( modifier = Modifier .padding(top = 8.dp) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/InfoDialog.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/InfoDialog.kt similarity index 86% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/InfoDialog.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/InfoDialog.kt index f1a4933e9..9695d2d12 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/InfoDialog.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/InfoDialog.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.dialog +package dev.minios.pdaiv1.presentation.widget.dialog import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.AlertDialog @@ -10,9 +10,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.localization.R -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString +import dev.minios.pdaiv1.core.localization.R +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString @Composable fun InfoDialog( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/ProgressDialog.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/ProgressDialog.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/ProgressDialog.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/ProgressDialog.kt index 91add3589..1b61a1cbd 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/dialog/ProgressDialog.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/dialog/ProgressDialog.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.dialog +package dev.minios.pdaiv1.presentation.widget.dialog import androidx.annotation.StringRes import androidx.compose.foundation.layout.Box @@ -24,8 +24,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import com.shifthackz.aisdv1.core.localization.formatter.DurationFormatter -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.localization.formatter.DurationFormatter +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ProgressDialog( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionComponent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionComponent.kt new file mode 100644 index 000000000..4c2837f0d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionComponent.kt @@ -0,0 +1,91 @@ +package dev.minios.pdaiv1.presentation.widget.engine + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.minios.pdaiv1.core.model.asUiText +import com.shifthackz.android.core.mvi.MviComponent +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.widget.input.DropdownTextField +import org.koin.androidx.compose.koinViewModel +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun EngineSelectionComponent( + modifier: Modifier = Modifier, +) { + MviComponent( + viewModel = koinViewModel(), + ) { state, intentHandler -> + when (state.mode) { + ServerSource.AUTOMATIC1111 -> DropdownTextField( + label = LocalizationR.string.hint_sd_model.asUiText(), + loading = state.loading, + modifier = modifier, + value = state.selectedSdModel, + items = state.sdModels, + onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, + ) + + ServerSource.SWARM_UI -> DropdownTextField( + label = LocalizationR.string.hint_sd_model.asUiText(), + loading = state.loading, + modifier = modifier, + value = state.selectedSwarmModel, + items = state.swarmModels, + onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, + ) + + ServerSource.HUGGING_FACE -> DropdownTextField( + label = LocalizationR.string.hint_hugging_face_model.asUiText(), + loading = state.loading, + modifier = modifier, + value = state.selectedHfModel, + items = state.hfModels, + onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, + ) + + ServerSource.STABILITY_AI -> DropdownTextField( + label = LocalizationR.string.hint_stability_ai_engine.asUiText(), + loading = state.loading, + modifier = modifier, + value = state.selectedStEngine, + items = state.stEngines, + onItemSelected = { intentHandler(EngineSelectionIntent(it)) }, + ) + + ServerSource.LOCAL_MICROSOFT_ONNX -> DropdownTextField( + label = LocalizationR.string.hint_sd_model.asUiText(), + loading = state.loading, + modifier = modifier, + value = state.localAiModels.firstOrNull { it.id == state.selectedLocalAiModelId }, + items = state.localAiModels, + onItemSelected = { intentHandler(EngineSelectionIntent(it.id)) }, + displayDelegate = { it.name.asUiText() }, + ) + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> DropdownTextField( + label = LocalizationR.string.hint_sd_model.asUiText(), + loading = state.loading, + modifier = modifier, + value = state.mediaPipeModels.firstOrNull { it.id == state.selectedMediaPipeModelId }, + items = state.mediaPipeModels, + onItemSelected = { intentHandler(EngineSelectionIntent(it.id)) }, + displayDelegate = { it.name.asUiText() }, + ) + + ServerSource.LOCAL_QUALCOMM_QNN -> DropdownTextField( + label = LocalizationR.string.hint_sd_model.asUiText(), + loading = state.loading, + modifier = modifier, + value = state.qnnModels.firstOrNull { it.id == state.selectedQnnModelId }, + items = state.qnnModels, + onItemSelected = { intentHandler(EngineSelectionIntent(it.id)) }, + displayDelegate = { it.name.asUiText() }, + ) + + ServerSource.HORDE -> Unit + ServerSource.OPEN_AI -> Unit + ServerSource.FAL_AI -> Unit + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionIntent.kt new file mode 100644 index 000000000..fdb606d09 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionIntent.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.presentation.widget.engine + +import com.shifthackz.android.core.mvi.MviIntent + +data class EngineSelectionIntent(val value: String) : MviIntent diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionState.kt new file mode 100644 index 000000000..1cd15f315 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionState.kt @@ -0,0 +1,26 @@ +package dev.minios.pdaiv1.presentation.widget.engine + +import androidx.compose.runtime.Immutable +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.entity.ServerSource +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class EngineSelectionState( + val loading: Boolean = true, + val mode: ServerSource = ServerSource.AUTOMATIC1111, + val sdModels: List = emptyList(), + val selectedSdModel: String = "", + val swarmModels: List = emptyList(), + val selectedSwarmModel: String = "", + val hfModels: List = emptyList(), + val selectedHfModel: String = "", + val stEngines: List = emptyList(), + val selectedStEngine: String = "", + val localAiModels: List = emptyList(), + val selectedLocalAiModelId: String = "", + val mediaPipeModels: List = emptyList(), + val selectedMediaPipeModelId: String = "", + val qnnModels: List = emptyList(), + val selectedQnnModelId: String = "", +): MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionViewModel.kt new file mode 100644 index 000000000..be7093cd2 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/EngineSelectionViewModel.kt @@ -0,0 +1,209 @@ +package dev.minios.pdaiv1.presentation.widget.engine + +import dev.minios.pdaiv1.core.common.extensions.EmptyLambda +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.model.Octagonal +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalQnnModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.ObserveLocalOnnxModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.ScanCustomModelsUseCase +import dev.minios.pdaiv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase +import dev.minios.pdaiv1.domain.usecase.sdmodel.GetStableDiffusionModelsUseCase +import dev.minios.pdaiv1.domain.usecase.sdmodel.SelectStableDiffusionModelUseCase +import dev.minios.pdaiv1.domain.usecase.settings.GetConfigurationUseCase +import dev.minios.pdaiv1.domain.usecase.stabilityai.FetchAndGetStabilityAiEnginesUseCase +import dev.minios.pdaiv1.domain.usecase.swarmmodel.FetchAndGetSwarmUiModelsUseCase +import com.shifthackz.android.core.mvi.EmptyEffect +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.kotlin.subscribeBy + +class EngineSelectionViewModel( + dispatchersProvider: DispatchersProvider, + fetchAndGetSwarmUiModelsUseCase: FetchAndGetSwarmUiModelsUseCase, + observeLocalOnnxModelsUseCase: ObserveLocalOnnxModelsUseCase, + fetchAndGetStabilityAiEnginesUseCase: FetchAndGetStabilityAiEnginesUseCase, + getHuggingFaceModelsUseCase: FetchAndGetHuggingFaceModelsUseCase, + getLocalMediaPipeModelsUseCase: GetLocalMediaPipeModelsUseCase, + getLocalQnnModelsUseCase: GetLocalQnnModelsUseCase, + scanCustomModelsUseCase: ScanCustomModelsUseCase, + private val preferenceManager: PreferenceManager, + private val schedulersProvider: SchedulersProvider, + private val getConfigurationUseCase: GetConfigurationUseCase, + private val selectStableDiffusionModelUseCase: SelectStableDiffusionModelUseCase, + private val getStableDiffusionModelsUseCase: GetStableDiffusionModelsUseCase, +) : MviRxViewModel() { + + override val initialState = EngineSelectionState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + val configuration = preferenceManager + .observe() + .flatMap { getConfigurationUseCase().toFlowable() } + .onErrorReturn { Configuration() } + + val a1111Models = getStableDiffusionModelsUseCase() + .onErrorReturn { emptyList() } + .toFlowable() + + val swarmModels = fetchAndGetSwarmUiModelsUseCase() + .onErrorReturn { emptyList() } + .toFlowable() + + val huggingFaceModels = getHuggingFaceModelsUseCase() + .onErrorReturn { emptyList() } + .toFlowable() + + val stabilityAiEngines = fetchAndGetStabilityAiEnginesUseCase() + .onErrorReturn { emptyList() } + .toFlowable() + + // Combine downloaded ONNX models with scanned custom models + val localAiModels = observeLocalOnnxModelsUseCase() + .flatMap { downloadedModels -> + scanCustomModelsUseCase(LocalAiModel.Type.ONNX) + .map { scannedModels -> + val downloaded = downloadedModels.filter { it.downloaded } + downloaded.filter { it.id != LocalAiModel.CustomOnnx.id } + scannedModels + } + .onErrorReturn { downloadedModels.filter { it.downloaded } } + .toFlowable() + } + .onErrorReturn { emptyList() } + + // Combine downloaded MediaPipe models with scanned custom models + val mediaPipeModels = getLocalMediaPipeModelsUseCase() + .flatMap { downloadedModels -> + scanCustomModelsUseCase(LocalAiModel.Type.MediaPipe) + .map { scannedModels -> + val downloaded = downloadedModels.filter { it.downloaded } + downloaded.filter { it.id != LocalAiModel.CustomMediaPipe.id } + scannedModels + } + .onErrorReturn { downloadedModels.filter { it.downloaded } } + } + .onErrorReturn { emptyList() } + .toFlowable() + + // Combine downloaded QNN models with scanned custom models + val qnnModels = getLocalQnnModelsUseCase() + .flatMap { downloadedModels -> + scanCustomModelsUseCase(LocalAiModel.Type.QNN) + .map { scannedModels -> + val downloaded = downloadedModels.filter { it.downloaded } + downloaded.filter { it.id != LocalAiModel.CustomQnn.id } + scannedModels + } + .onErrorReturn { downloadedModels.filter { it.downloaded } } + } + .onErrorReturn { emptyList() } + .toFlowable() + + !Flowable.combineLatest( + configuration, + a1111Models, + swarmModels, + huggingFaceModels, + stabilityAiEngines, + localAiModels, + mediaPipeModels, + qnnModels, + ::Octagonal, + ) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy( + onError = ::errorLog, + onComplete = EmptyLambda, + onNext = { (config, sdModels, swarmModels, hfModels, stEngines, localModels, mpModels, qnnModels) -> + updateState { state -> + state.copy( + loading = false, + mode = config.source, + sdModels = sdModels.map { it.first.title }, + selectedSdModel = sdModels.firstOrNull { it.second }?.first?.title + ?: state.selectedSdModel, + swarmModels = swarmModels.map { it.name }, + selectedSwarmModel = swarmModels.firstOrNull { it.name == config.swarmUiModel }?.name + ?: state.selectedSwarmModel, + hfModels = hfModels.map { it.alias }, + selectedHfModel = config.huggingFaceModel, + stEngines = stEngines.map { it.id }, + selectedStEngine = config.stabilityAiEngineId, + localAiModels = localModels, + selectedLocalAiModelId = localModels.firstOrNull { it.id == config.localOnnxModelId }?.id + ?: localModels.firstOrNull()?.id + ?: state.selectedLocalAiModelId, + mediaPipeModels = mpModels, + selectedMediaPipeModelId = mpModels.firstOrNull { it.id == config.localMediaPipeModelId }?.id + ?: mpModels.firstOrNull()?.id + ?: state.selectedMediaPipeModelId, + qnnModels = qnnModels, + selectedQnnModelId = qnnModels.firstOrNull { it.id == config.localQnnModelId }?.id + ?: qnnModels.firstOrNull()?.id + ?: state.selectedQnnModelId, + ) + } + }, + ) + } + + override fun processIntent(intent: EngineSelectionIntent) { + when (currentState.mode) { + ServerSource.AUTOMATIC1111 -> !selectStableDiffusionModelUseCase(intent.value) + .doOnSubscribe { + updateState { + it.copy( + loading = true, + selectedSdModel = intent.value, + ) + } + } + .andThen(getStableDiffusionModelsUseCase()) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { sdModels -> + updateState { state -> + state.copy( + loading = false, + sdModels = sdModels.map { it.first.title }, + selectedSdModel = sdModels.first { it.second }.first.title, + ) + } + } + + ServerSource.SWARM_UI -> preferenceManager.swarmUiModel = intent.value + + ServerSource.HUGGING_FACE -> preferenceManager.huggingFaceModel = intent.value + + ServerSource.STABILITY_AI -> preferenceManager.stabilityAiEngineId = intent.value + + ServerSource.LOCAL_MICROSOFT_ONNX -> { + preferenceManager.localOnnxModelId = intent.value + updateState { it.copy(selectedLocalAiModelId = intent.value) } + } + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> { + preferenceManager.localMediaPipeModelId = intent.value + updateState { it.copy(selectedMediaPipeModelId = intent.value) } + } + + ServerSource.LOCAL_QUALCOMM_QNN -> { + preferenceManager.localQnnModelId = intent.value + // Update runOnCpu based on selected model + val selectedModel = currentState.qnnModels.find { it.id == intent.value } + if (selectedModel != null) { + preferenceManager.localQnnRunOnCpu = selectedModel.runOnCpu + } + updateState { it.copy(selectedQnnModelId = intent.value) } + } + + else -> Unit + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/QnnRuntimeSelectionComponent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/QnnRuntimeSelectionComponent.kt new file mode 100644 index 000000000..0518c8a5f --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/engine/QnnRuntimeSelectionComponent.kt @@ -0,0 +1,98 @@ +package dev.minios.pdaiv1.presentation.widget.engine + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.FilterChip +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import org.koin.compose.koinInject +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +/** + * Component for selecting QNN runtime mode (CPU or GPU/OpenCL). + * Only shown when QNN backend is active and model supports CPU mode. + */ +@Composable +fun QnnRuntimeSelectionComponent( + modifier: Modifier = Modifier, +) { + val preferenceManager: PreferenceManager = koinInject() + var useOpenCL by remember { mutableStateOf(preferenceManager.localQnnUseOpenCL) } + var showGpuWarningDialog by remember { mutableStateOf(false) } + + Row( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = stringResource(id = LocalizationR.string.hint_runtime), + style = MaterialTheme.typography.bodyMedium + ) + FilterChip( + selected = !useOpenCL, + onClick = { + useOpenCL = false + preferenceManager.localQnnUseOpenCL = false + }, + label = { Text("CPU") }, + modifier = Modifier.weight(1f) + ) + FilterChip( + selected = useOpenCL, + onClick = { + if (!useOpenCL) { + showGpuWarningDialog = true + } else { + useOpenCL = false + preferenceManager.localQnnUseOpenCL = false + } + }, + label = { Text("GPU") }, + modifier = Modifier.weight(1f) + ) + } + + if (showGpuWarningDialog) { + AlertDialog( + onDismissRequest = { showGpuWarningDialog = false }, + containerColor = MaterialTheme.colorScheme.surface, + titleContentColor = MaterialTheme.colorScheme.onSurface, + textContentColor = MaterialTheme.colorScheme.onSurfaceVariant, + title = { Text(stringResource(id = LocalizationR.string.warning_gpu_runtime_title)) }, + text = { Text(stringResource(id = LocalizationR.string.warning_gpu_runtime_message)) }, + confirmButton = { + TextButton( + onClick = { + showGpuWarningDialog = false + useOpenCL = true + preferenceManager.localQnnUseOpenCL = true + } + ) { + Text(stringResource(id = LocalizationR.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = { showGpuWarningDialog = false }) { + Text(stringResource(id = LocalizationR.string.cancel)) + } + } + ) + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/error/ErrorComposable.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/error/ErrorComposable.kt similarity index 85% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/error/ErrorComposable.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/error/ErrorComposable.kt index 085524efd..1dad09f67 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/error/ErrorComposable.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/error/ErrorComposable.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.error +package dev.minios.pdaiv1.presentation.widget.error import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -11,9 +11,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun ErrorComposable( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiDynamicForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiDynamicForm.kt new file mode 100644 index 000000000..f4b0eb58b --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiDynamicForm.kt @@ -0,0 +1,249 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package dev.minios.pdaiv1.presentation.widget.falai + +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.ExpandLess +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType +import dev.minios.pdaiv1.presentation.model.FalAiEndpointUi +import dev.minios.pdaiv1.presentation.model.FalAiPropertyUi +import dev.minios.pdaiv1.presentation.screen.falai.FalAiGenerationState +import dev.minios.pdaiv1.presentation.widget.input.DropdownTextField + +@Composable +fun FalAiDynamicForm( + state: FalAiGenerationState, + onEndpointSelected: (String) -> Unit, + onPropertyChanged: (String, Any?) -> Unit, + onToggleAdvanced: (Boolean) -> Unit, + onImportOpenApi: (String) -> Unit = {}, + onDeleteEndpoint: (String) -> Unit = {}, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + // Endpoint selector with import button + FalAiEndpointSelector( + endpoints = state.endpoints, + selectedEndpoint = state.selectedEndpoint, + onEndpointSelected = onEndpointSelected, + onImportOpenApi = onImportOpenApi, + onDeleteEndpoint = onDeleteEndpoint, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Main properties (prompt, image fields, etc.) + state.mainProperties.forEach { property -> + if (property.type == FalAiPropertyType.INPAINT) { + // Special handling for inpainting - combined image + mask field + FalAiInpaintField( + property = property, + currentImageValue = state.propertyValues[property.name], + currentMaskValue = property.linkedMaskProperty?.let { state.propertyValues[it] }, + onImageChange = { value -> onPropertyChanged(property.name, value) }, + onMaskChange = { value -> + property.linkedMaskProperty?.let { maskProp -> + onPropertyChanged(maskProp, value) + } + }, + ) + } else { + FalAiPropertyField( + property = property, + onValueChange = { value -> onPropertyChanged(property.name, value) }, + ) + } + } + + // Advanced options section + if (state.hasAdvancedProperties) { + Spacer(modifier = Modifier.height(16.dp)) + + AdvancedOptionsSection( + visible = state.advancedOptionsVisible, + onToggle = onToggleAdvanced, + properties = state.advancedProperties, + onPropertyChanged = onPropertyChanged, + ) + } + } +} + +@Composable +fun FalAiEndpointSelector( + endpoints: List, + selectedEndpoint: FalAiEndpointUi?, + onEndpointSelected: (String) -> Unit, + onImportOpenApi: (String) -> Unit = {}, + onDeleteEndpoint: (String) -> Unit = {}, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + + val jsonFilePicker = rememberLauncherForActivityResult( + ActivityResultContracts.GetContent() + ) { uri -> + uri ?: return@rememberLauncherForActivityResult + try { + context.contentResolver.openInputStream(uri)?.use { inputStream -> + val json = inputStream.bufferedReader().readText() + onImportOpenApi(json) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + Column(modifier = modifier) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Model", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + + // Import button + OutlinedButton( + onClick = { jsonFilePicker.launch("application/json") }, + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.padding(end = 4.dp), + ) + Text("Import") + } + + // Delete button (only for custom endpoints) + if (selectedEndpoint?.isCustom == true) { + Spacer(modifier = Modifier.width(8.dp)) + IconButton( + onClick = { onDeleteEndpoint(selectedEndpoint.id) }, + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Delete endpoint", + tint = MaterialTheme.colorScheme.error, + ) + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Sort endpoints by title + val sortedEndpoints = endpoints.sortedBy { it.title } + + DropdownTextField( + modifier = Modifier.fillMaxWidth(), + label = "Select endpoint".asUiText(), + value = selectedEndpoint, + items = sortedEndpoints, + onItemSelected = { endpoint -> onEndpointSelected(endpoint.id) }, + displayDelegate = { endpoint -> + val customSuffix = if (endpoint.isCustom) " ⭐" else "" + "${endpoint.title} (${endpoint.category})$customSuffix".asUiText() + }, + ) + + selectedEndpoint?.description?.takeIf { it.isNotBlank() }?.let { description -> + Text( + text = description, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 8.dp, start = 4.dp), + ) + } + } +} + +@Composable +private fun AdvancedOptionsSection( + visible: Boolean, + onToggle: (Boolean) -> Unit, + properties: List, + onPropertyChanged: (String, Any?) -> Unit, +) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), + ), + ) { + Column { + // Header (always visible) + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onToggle(!visible) } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Advanced Options", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = if (visible) Icons.Default.ExpandLess else Icons.Default.ExpandMore, + contentDescription = if (visible) "Collapse" else "Expand", + ) + } + + // Content (animated visibility) + AnimatedVisibility( + visible = visible, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + Column( + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + ), + ) { + properties.forEach { property -> + FalAiPropertyField( + property = property, + onValueChange = { value -> onPropertyChanged(property.name, value) }, + ) + } + } + } + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiInpaintField.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiInpaintField.kt new file mode 100644 index 000000000..92e5fe007 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiInpaintField.kt @@ -0,0 +1,249 @@ +package dev.minios.pdaiv1.presentation.widget.falai + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Undo +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.CleaningServices +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.presentation.model.FalAiPropertyUi +import dev.minios.pdaiv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.screen.inpaint.components.InPaintComponent +import dev.minios.pdaiv1.presentation.utils.uriToBitmap +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream + +/** + * Combined image + mask drawing field for inpainting endpoints. + * Shows the image and allows drawing a mask on top. + */ +@Composable +fun FalAiInpaintField( + property: FalAiPropertyUi, + currentImageValue: Any?, + currentMaskValue: Any?, + onImageChange: (String) -> Unit, + onMaskChange: (String) -> Unit, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + var imageBitmap by remember { mutableStateOf(null) } + var inPaintModel by remember { mutableStateOf(InPaintModel()) } + var brushSize by remember { mutableFloatStateOf(24f) } + + // Restore image from currentValue if it's a base64 string + LaunchedEffect(currentImageValue) { + val imageUrl = currentImageValue?.toString() ?: "" + if (imageUrl.startsWith("data:image") && imageBitmap == null) { + withContext(Dispatchers.IO) { + val bitmap = base64DataUriToBitmap(imageUrl) + withContext(Dispatchers.Main) { + imageBitmap = bitmap + } + } + } else if (imageUrl.isBlank()) { + imageBitmap = null + inPaintModel = InPaintModel() + } + } + + val mediaPicker = rememberLauncherForActivityResult( + ActivityResultContracts.PickVisualMedia(), + ) { uri -> + val bitmap = uri?.let { uriToBitmap(context, it) } ?: return@rememberLauncherForActivityResult + imageBitmap = bitmap + // Clear existing mask when new image is selected + inPaintModel = InPaintModel() + onMaskChange("") + // Convert to base64 data URI + val base64 = bitmapToBase64DataUri(bitmap) + onImageChange(base64) + } + + Column(modifier = modifier.fillMaxWidth()) { + Text( + text = property.title, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(bottom = 8.dp), + ) + + if (property.description.isNotBlank()) { + Text( + text = property.description, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = 8.dp), + ) + } + + if (imageBitmap != null) { + // Show inpainting canvas + InPaintComponent( + modifier = Modifier.fillMaxWidth(), + drawMode = true, + inPaint = inPaintModel, + bitmap = imageBitmap, + capWidth = brushSize.toInt(), + onPathDrawn = { path -> + inPaintModel = inPaintModel.copy( + paths = inPaintModel.paths + (path to brushSize.toInt()) + ) + }, + onPathBitmapDrawn = { maskBitmap -> + if (maskBitmap != null) { + val maskBase64 = bitmapToBase64DataUri(maskBitmap) + onMaskChange(maskBase64) + } + }, + ) + + Spacer(modifier = Modifier.height(12.dp)) + + // Brush size slider + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Brush size: ${brushSize.toInt()}", + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = brushSize, + onValueChange = { brushSize = it }, + valueRange = 4f..64f, + modifier = Modifier.fillMaxWidth(), + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Action buttons + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedButton( + onClick = { + if (inPaintModel.paths.isNotEmpty()) { + inPaintModel = inPaintModel.copy( + paths = inPaintModel.paths.dropLast(1) + ) + } + }, + enabled = inPaintModel.paths.isNotEmpty(), + modifier = Modifier.weight(1f), + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.Undo, + contentDescription = "Undo", + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = "Undo", + ) + } + + Spacer(modifier = Modifier.width(8.dp)) + + OutlinedButton( + onClick = { + inPaintModel = InPaintModel() + onMaskChange("") + }, + enabled = inPaintModel.paths.isNotEmpty(), + modifier = Modifier.weight(1f), + ) { + Icon( + imageVector = Icons.Default.CleaningServices, + contentDescription = "Clear", + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = "Clear", + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Change image button + OutlinedButton( + onClick = { + mediaPicker.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }, + modifier = Modifier.fillMaxWidth(), + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.padding(end = 8.dp), + ) + Text("Change Image") + } + } else { + // No image selected - show picker button + OutlinedButton( + onClick = { + mediaPicker.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }, + modifier = Modifier.fillMaxWidth(), + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.padding(end = 8.dp), + ) + Text("Select Image for Inpainting") + } + } + } +} + +private fun bitmapToBase64DataUri(bitmap: Bitmap): String { + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + val base64 = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP) + return "data:image/png;base64,$base64" +} + +private fun base64DataUriToBitmap(dataUri: String): Bitmap? { + return try { + val base64Image = dataUri.substringAfter("base64,") + val decodedString = Base64.decode(base64Image, Base64.DEFAULT) + BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size) + } catch (e: Exception) { + e.printStackTrace() + null + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiPropertyField.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiPropertyField.kt new file mode 100644 index 000000000..8f7c886ff --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/falai/FalAiPropertyField.kt @@ -0,0 +1,973 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package dev.minios.pdaiv1.presentation.widget.falai + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Image +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.FalAiPropertyType +import dev.minios.pdaiv1.presentation.model.FalAiPropertyUi +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.utils.uriToBitmap +import dev.minios.pdaiv1.presentation.widget.input.DropdownTextField +import dev.minios.pdaiv1.presentation.widget.input.SliderTextInputField +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream + +private const val CUSTOM_SIZE_OPTION = "custom" + +@Composable +fun FalAiPropertyField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.padding(vertical = 4.dp)) { + when (property.type) { + FalAiPropertyType.STRING -> FalAiStringField(property, onValueChange) + FalAiPropertyType.INTEGER -> FalAiIntegerField(property, onValueChange) + FalAiPropertyType.NUMBER -> FalAiNumberField(property, onValueChange) + FalAiPropertyType.BOOLEAN -> FalAiBooleanField(property, onValueChange) + FalAiPropertyType.ENUM -> FalAiEnumField(property, onValueChange) + FalAiPropertyType.IMAGE_URL -> FalAiImageUrlField(property, onValueChange) + FalAiPropertyType.IMAGE_URL_ARRAY -> FalAiImageUrlArrayField(property, onValueChange) + FalAiPropertyType.IMAGE_SIZE -> FalAiImageSizeField(property, onValueChange) + FalAiPropertyType.ARRAY -> { + if (property.arrayItemProperties != null) { + FalAiArrayField(property, onValueChange) + } else { + // Fallback to text field for unsupported array types + FalAiStringField(property, onValueChange) + } + } + FalAiPropertyType.OBJECT -> { + // For now, show as text field for JSON input + FalAiStringField(property, onValueChange) + } + FalAiPropertyType.INPAINT -> { + // INPAINT is handled separately in FalAiDynamicForm with FalAiInpaintField + // This fallback shows a simple image picker if used standalone + FalAiImageUrlField(property, onValueChange) + } + } + if (property.description.isNotBlank()) { + Text( + text = property.description, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(start = 8.dp, top = 4.dp), + ) + } + } +} + +@Composable +private fun FalAiStringField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + var text by remember { mutableStateOf(property.currentValue?.toString() ?: "") } + + LaunchedEffect(property.currentValue) { + text = property.currentValue?.toString() ?: "" + } + + val isPromptField = property.name == "prompt" + + TextField( + modifier = Modifier.fillMaxWidth(), + value = text, + onValueChange = { newValue -> + text = newValue + onValueChange(newValue) + }, + label = { Text(property.title) }, + minLines = if (isPromptField) 3 else 1, + maxLines = if (isPromptField) 6 else 3, + singleLine = !isPromptField, + keyboardOptions = KeyboardOptions( + imeAction = if (isPromptField) ImeAction.Default else ImeAction.Done, + ), + colors = textFieldColors, + ) +} + +@Composable +private fun FalAiIntegerField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + val min = property.minimum ?: 0.0 + val max = property.maximum ?: 100.0 + val hasRange = property.minimum != null && property.maximum != null + + if (hasRange) { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = property.title, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 4.dp), + ) + SliderTextInputField( + value = (property.currentValue as? Number)?.toFloat() ?: min.toFloat(), + onValueChange = { onValueChange(it.toInt()) }, + valueRange = min.toFloat()..max.toFloat(), + fractionDigits = 0, + ) + } + } else { + var text by remember { mutableStateOf(property.currentValue?.toString() ?: "") } + + LaunchedEffect(property.currentValue) { + text = property.currentValue?.toString() ?: "" + } + + TextField( + modifier = Modifier.fillMaxWidth(), + value = text, + onValueChange = { newValue -> + text = newValue + newValue.toIntOrNull()?.let { onValueChange(it) } + }, + label = { Text(property.title) }, + singleLine = true, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done, + ), + colors = textFieldColors, + ) + } +} + +@Composable +private fun FalAiNumberField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + val min = property.minimum ?: 0.0 + val max = property.maximum ?: 100.0 + val hasRange = property.minimum != null && property.maximum != null + + if (hasRange) { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = property.title, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 4.dp), + ) + SliderTextInputField( + value = (property.currentValue as? Number)?.toDouble() ?: min, + onValueChange = { onValueChange(it) }, + valueRange = min..max, + fractionDigits = 2, + ) + } + } else { + var text by remember { mutableStateOf(property.currentValue?.toString() ?: "") } + + LaunchedEffect(property.currentValue) { + text = property.currentValue?.toString() ?: "" + } + + TextField( + modifier = Modifier.fillMaxWidth(), + value = text, + onValueChange = { newValue -> + text = newValue + newValue.toDoubleOrNull()?.let { onValueChange(it) } + }, + label = { Text(property.title) }, + singleLine = true, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Decimal, + imeAction = ImeAction.Done, + ), + colors = textFieldColors, + ) + } +} + +@Composable +private fun FalAiBooleanField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = property.title, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.weight(1f), + ) + Spacer(modifier = Modifier.width(16.dp)) + Switch( + checked = property.currentValue as? Boolean ?: false, + onCheckedChange = { onValueChange(it) }, + ) + } +} + +@Composable +private fun FalAiEnumField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + DropdownTextField( + modifier = Modifier.fillMaxWidth(), + label = property.title.asUiText(), + value = property.currentValue?.toString() ?: property.enumValues.firstOrNull(), + items = property.enumValues, + onItemSelected = { onValueChange(it) }, + ) +} + +@Composable +private fun FalAiImageUrlField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + val context = LocalContext.current + var imageUrl by remember { mutableStateOf(property.currentValue?.toString() ?: "") } + var previewBitmap by remember { mutableStateOf(null) } + + LaunchedEffect(property.currentValue) { + val newUrl = property.currentValue?.toString() ?: "" + imageUrl = newUrl + if (newUrl.startsWith("data:image") && previewBitmap == null) { + withContext(Dispatchers.IO) { + val bitmap = base64DataUriToBitmap(newUrl) + withContext(Dispatchers.Main) { + previewBitmap = bitmap + } + } + } else if (newUrl.isBlank()) { + previewBitmap = null + } + } + + val mediaPicker = rememberLauncherForActivityResult( + ActivityResultContracts.PickVisualMedia(), + ) { uri -> + val bitmap = uri?.let { uriToBitmap(context, it) } ?: return@rememberLauncherForActivityResult + previewBitmap = bitmap + // Convert to base64 data URI + val base64 = bitmapToBase64DataUri(bitmap) + imageUrl = base64 + onValueChange(base64) + } + + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = property.title, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp), + ) + + // Show preview if we have an image (compact row layout like multi-image) + if (previewBitmap != null || imageUrl.startsWith("data:image") || imageUrl.startsWith("http")) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + // Image preview (compact) + Box( + modifier = Modifier + .size(80.dp) + .clip(RoundedCornerShape(8.dp)) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outline, + shape = RoundedCornerShape(8.dp), + ), + ) { + previewBitmap?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Selected image", + modifier = Modifier.fillMaxWidth(), + contentScale = ContentScale.Crop, + ) + } ?: run { + // For URL-based images, just show icon + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Default.Image, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + // Image info + Column(modifier = Modifier.weight(1f)) { + Text( + text = property.title, + style = MaterialTheme.typography.bodyMedium, + ) + Text( + text = if (imageUrl.startsWith("data:image")) "Local image" else imageUrl.take(30) + if (imageUrl.length > 30) "..." else "", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + // Remove button + IconButton( + onClick = { + previewBitmap = null + imageUrl = "" + onValueChange("") + }, + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Remove image", + tint = MaterialTheme.colorScheme.error, + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Change image button + OutlinedButton( + onClick = { + mediaPicker.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }, + modifier = Modifier.fillMaxWidth(), + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.padding(end = 8.dp), + ) + Text("Change Image") + } + } else { + // Add image button (like multi-image) + OutlinedButton( + onClick = { + mediaPicker.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }, + modifier = Modifier.fillMaxWidth(), + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.padding(end = 8.dp), + ) + Text("Add Image") + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // URL input field (for pasting URLs) + TextField( + modifier = Modifier.fillMaxWidth(), + value = if (imageUrl.startsWith("data:image")) "" else imageUrl, + onValueChange = { newValue -> + if (!newValue.startsWith("data:image")) { + imageUrl = newValue + previewBitmap = null + onValueChange(newValue) + } + }, + label = { Text("Or paste image URL") }, + placeholder = { Text("https://...") }, + singleLine = true, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + ), + colors = textFieldColors, + ) + } +} + +/** + * Converts a Bitmap to a base64 data URI string. + */ +private fun bitmapToBase64DataUri(bitmap: Bitmap): String { + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + val base64 = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP) + return "data:image/png;base64,$base64" +} + +/** + * Field for an array of image URLs (e.g., image_urls for flux-2/edit). + * Supports adding multiple images via picker or URL input. + */ +@Composable +private fun FalAiImageUrlArrayField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + val context = LocalContext.current + + // Parse current value as list of strings + @Suppress("UNCHECKED_CAST") + val initialImages = when (val current = property.currentValue) { + is List<*> -> current.filterIsInstance().toMutableList() + else -> mutableListOf() + } + + var images by remember { mutableStateOf(initialImages) } + var bitmaps by remember { mutableStateOf>(emptyMap()) } + + LaunchedEffect(property.currentValue) { + @Suppress("UNCHECKED_CAST") + val newImages = when (val current = property.currentValue) { + is List<*> -> current.filterIsInstance().toMutableList() + else -> mutableListOf() + } + if (newImages != images) { + images = newImages + } + + // Restore bitmaps if missing or out of sync + if (bitmaps.size != newImages.size || bitmaps.keys.any { it >= newImages.size }) { + withContext(Dispatchers.IO) { + val newBitmaps = mutableMapOf() + newImages.forEachIndexed { index, url -> + if (url.startsWith("data:image")) { + base64DataUriToBitmap(url)?.let { + newBitmaps[index] = it + } + } + } + withContext(Dispatchers.Main) { + bitmaps = newBitmaps + } + } + } + } + + val mediaPicker = rememberLauncherForActivityResult( + ActivityResultContracts.PickMultipleVisualMedia(maxItems = 4), + ) { uris -> + val newBitmaps = mutableMapOf() + val newUrls = uris.mapNotNull { uri -> + uriToBitmap(context, uri)?.let { bitmap -> + val index = images.size + newBitmaps.size + newBitmaps[index] = bitmap + bitmapToBase64DataUri(bitmap) + } + } + if (newUrls.isNotEmpty()) { + bitmaps = bitmaps + newBitmaps + images = (images + newUrls).toMutableList() + onValueChange(images) + } + } + + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = property.title, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp), + ) + + // Show existing images in a grid-like layout + if (images.isNotEmpty()) { + Column { + images.forEachIndexed { index, imageUrl -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + // Image preview + Box( + modifier = Modifier + .size(80.dp) + .clip(RoundedCornerShape(8.dp)) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outline, + shape = RoundedCornerShape(8.dp), + ), + ) { + bitmaps[index]?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Image ${index + 1}", + modifier = Modifier.fillMaxWidth(), + contentScale = ContentScale.Crop, + ) + } ?: run { + // For URL-based images, just show icon + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Default.Image, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + // Image info + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Image ${index + 1}", + style = MaterialTheme.typography.bodyMedium, + ) + Text( + text = if (imageUrl.startsWith("data:image")) "Local image" else imageUrl.take(30) + "...", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + // Remove button + IconButton( + onClick = { + bitmaps = bitmaps - index + images = images.toMutableList().apply { removeAt(index) } + onValueChange(images) + }, + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Remove image", + tint = MaterialTheme.colorScheme.error, + ) + } + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + } + + // Add image button + if (images.size < 4) { + OutlinedButton( + onClick = { + mediaPicker.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }, + modifier = Modifier.fillMaxWidth(), + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.padding(end = 8.dp), + ) + Text("Add Image${if (images.isEmpty()) "s" else ""} (${images.size}/4)") + } + } else { + Text( + text = "Maximum 4 images reached", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 4.dp), + ) + } + } +} + +@Composable +private fun FalAiImageSizeField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + // Determine initial state from currentValue + val initialValue = property.currentValue + val initialIsCustom = initialValue is Map<*, *> + val initialPreset = if (initialIsCustom) { + CUSTOM_SIZE_OPTION + } else { + initialValue?.toString() ?: property.defaultValue?.toString() ?: property.enumValues.firstOrNull() ?: "" + } + val initialWidth = if (initialValue is Map<*, *>) { + (initialValue["width"] as? Number)?.toInt() ?: 1024 + } else 1024 + val initialHeight = if (initialValue is Map<*, *>) { + (initialValue["height"] as? Number)?.toInt() ?: 1024 + } else 1024 + + var selectedPreset by remember { mutableStateOf(initialPreset) } + var isCustom by remember { mutableStateOf(initialIsCustom) } + var width by remember { mutableIntStateOf(initialWidth) } + var height by remember { mutableIntStateOf(initialHeight) } + + // Build dropdown options: presets + custom + val allOptions = remember(property.enumValues) { + property.enumValues + CUSTOM_SIZE_OPTION + } + + // Format preset names for display + val formatPresetName: (String) -> String = { preset -> + when (preset) { + CUSTOM_SIZE_OPTION -> "Custom" + else -> preset + } + } + + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = property.title, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp), + ) + + // Preset dropdown + DropdownTextField( + modifier = Modifier.fillMaxWidth(), + label = "Size preset".asUiText(), + value = selectedPreset, + items = allOptions, + onItemSelected = { preset -> + selectedPreset = preset + isCustom = preset == CUSTOM_SIZE_OPTION + if (isCustom) { + onValueChange(mapOf("width" to width, "height" to height)) + } else { + onValueChange(preset) + } + }, + displayDelegate = { formatPresetName(it).asUiText() }, + ) + + // Custom dimensions inputs (shown when custom is selected) + AnimatedVisibility( + visible = isCustom, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + Column(modifier = Modifier.padding(top = 12.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + // Width input + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Width", + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(bottom = 4.dp), + ) + TextField( + modifier = Modifier.fillMaxWidth(), + value = width.toString(), + onValueChange = { newValue -> + newValue.toIntOrNull()?.let { w -> + width = w.coerceIn(1, 14142) + onValueChange(mapOf("width" to width, "height" to height)) + } + }, + singleLine = true, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + colors = textFieldColors, + ) + } + + Spacer(modifier = Modifier.width(16.dp)) + + // Height input + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Height", + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(bottom = 4.dp), + ) + TextField( + modifier = Modifier.fillMaxWidth(), + value = height.toString(), + onValueChange = { newValue -> + newValue.toIntOrNull()?.let { h -> + height = h.coerceIn(1, 14142) + onValueChange(mapOf("width" to width, "height" to height)) + } + }, + singleLine = true, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done, + ), + colors = textFieldColors, + ) + } + } + + Text( + text = "Range: 1 - 14142 pixels", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 4.dp), + ) + } + } + } +} + +@Composable +private fun FalAiArrayField( + property: FalAiPropertyUi, + onValueChange: (Any?) -> Unit, +) { + val itemProperties = property.arrayItemProperties ?: return + + // Parse current value as list of maps + @Suppress("UNCHECKED_CAST") + val initialItems = when (val current = property.currentValue) { + is List<*> -> current.filterIsInstance>().toMutableList() + else -> mutableListOf() + } + + var items by remember { mutableStateOf(initialItems) } + + // Update items when currentValue changes externally + LaunchedEffect(property.currentValue) { + @Suppress("UNCHECKED_CAST") + val newItems = when (val current = property.currentValue) { + is List<*> -> current.filterIsInstance>().toMutableList() + else -> mutableListOf() + } + if (newItems != items) { + items = newItems + } + } + + Column(modifier = Modifier.fillMaxWidth()) { + // Header with title and add button + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = property.title, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + OutlinedButton( + onClick = { + // Create new item with default values + val newItem = itemProperties.associate { prop -> + prop.name to prop.defaultValue + } + items = (items + newItem).toMutableList() + onValueChange(items) + }, + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = "Add", + modifier = Modifier.padding(end = 4.dp), + ) + Text(text = "Add ${if (property.name == "loras") "LoRA" else "Item"}") + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // List of items + items.forEachIndexed { index, item -> + FalAiArrayItem( + index = index, + item = item, + itemProperties = itemProperties, + isLora = property.name == "loras", + onItemChange = { updatedItem -> + items = items.toMutableList().apply { + set(index, updatedItem) + } + onValueChange(items) + }, + onRemove = { + items = items.toMutableList().apply { + removeAt(index) + } + onValueChange(items) + }, + ) + if (index < items.size - 1) { + Spacer(modifier = Modifier.height(8.dp)) + } + } + + if (items.isEmpty()) { + Text( + text = "No items added yet", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(vertical = 16.dp), + ) + } + } +} + +@Composable +private fun FalAiArrayItem( + index: Int, + item: Map, + itemProperties: List, + isLora: Boolean, + onItemChange: (Map) -> Unit, + onRemove: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f), + shape = RoundedCornerShape(8.dp), + ) + .padding(12.dp), + ) { + // Item header with number and delete button + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = if (isLora) "LoRA ${index + 1}" else "Item ${index + 1}", + style = MaterialTheme.typography.titleSmall, + modifier = Modifier.weight(1f), + ) + IconButton(onClick = onRemove) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Remove", + tint = MaterialTheme.colorScheme.error, + ) + } + } + + // Render each property in the item + itemProperties.forEach { prop -> + val currentValue = item[prop.name] + val propertyWithValue = prop.copy(currentValue = currentValue) + + when (prop.type) { + FalAiPropertyType.STRING -> { + FalAiStringField(propertyWithValue) { newValue -> + onItemChange(item + (prop.name to newValue)) + } + } + FalAiPropertyType.NUMBER -> { + FalAiNumberField(propertyWithValue) { newValue -> + onItemChange(item + (prop.name to newValue)) + } + } + FalAiPropertyType.INTEGER -> { + FalAiIntegerField(propertyWithValue) { newValue -> + onItemChange(item + (prop.name to newValue)) + } + } + FalAiPropertyType.BOOLEAN -> { + FalAiBooleanField(propertyWithValue) { newValue -> + onItemChange(item + (prop.name to newValue)) + } + } + FalAiPropertyType.ENUM -> { + FalAiEnumField(propertyWithValue) { newValue -> + onItemChange(item + (prop.name to newValue)) + } + } + else -> { + // Default to string field for unsupported types + FalAiStringField(propertyWithValue) { newValue -> + onItemChange(item + (prop.name to newValue)) + } + } + } + } + } +} + +private fun base64DataUriToBitmap(dataUri: String): Bitmap? { + try { + val base64Image = dataUri.substringAfter("base64,") + val decodedString = Base64.decode(base64Image, Base64.DEFAULT) + return BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size) + } catch (e: Exception) { + e.printStackTrace() + return null + } +} + diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/frame/PhoneFrame.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/frame/PhoneFrame.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/frame/PhoneFrame.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/frame/PhoneFrame.kt index b30b8117b..a8c51b070 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/frame/PhoneFrame.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/frame/PhoneFrame.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.frame +package dev.minios.pdaiv1.presentation.widget.frame import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image @@ -21,7 +21,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.R +import dev.minios.pdaiv1.presentation.R @Composable fun PhoneFrame( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/image/ZoomableImage.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/image/ZoomableImage.kt new file mode 100644 index 000000000..b531afd88 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/image/ZoomableImage.kt @@ -0,0 +1,308 @@ +package dev.minios.pdaiv1.presentation.widget.image + +import android.graphics.Bitmap +import androidx.annotation.DrawableRes +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.calculatePan +import androidx.compose.foundation.gestures.calculateZoom +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlurEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.positionChanged +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import dev.minios.pdaiv1.presentation.R as PresentationR + +sealed interface ZoomableImageSource { + data class Bmp(val bitmap: Bitmap) : ZoomableImageSource + data class Resource(@DrawableRes val resId: Int) : ZoomableImageSource +} + +/** + * Zoomable image with Immich-style gesture handling: + * - Separate gesture recognizers for taps vs transforms (like Flutter's RawGestureDetector) + * - Double-tap to toggle zoom (initial ↔ 2x) + * - Single tap to toggle UI + * - Pinch to zoom + * - Pan when zoomed + * - Vertical swipe to dismiss/show info + */ +@Composable +fun ZoomableImage( + modifier: Modifier = Modifier, + source: ZoomableImageSource, + backgroundColor: Color = MaterialTheme.colorScheme.background, + minScale: Float = 1f, + maxScale: Float = 6f, + hideImage: Boolean = false, + hideBlurRadius: Float = 69f, + consumeGesturesWhenNotZoomed: Boolean = true, + onTap: () -> Unit = {}, + onSwipeUp: (() -> Unit)? = null, + onSwipeDown: (() -> Unit)? = null, + onDragProgress: ((Float) -> Unit)? = null, +) { + val configuration = LocalConfiguration.current + val density = LocalDensity.current.density + val width = configuration.screenWidthDp + val scope = rememberCoroutineScope() + + val initialScale = remember(source, density) { calculateInitialScale(source, width, density) } + val scale = remember { Animatable(initialScale) } + val offset = remember { Animatable(Offset.Zero, Offset.VectorConverter) } + + // Reset scale and offset when source changes (fixes artifact when swiping between pages) + LaunchedEffect(source) { + scale.snapTo(initialScale) + offset.snapTo(Offset.Zero) + } + + // Threshold to consider image as "zoomed" - slightly above initial scale + val zoomThreshold = initialScale * 1.05f + + // Target scale for double-tap zoom (2x from initial) + val doubleTapScale = initialScale * 2f + + // For vertical swipe detection (swipe-to-dismiss) + val swipeThreshold = 80f + val maxDragDistance = 400f + val verticalDragOffset = remember { Animatable(0f) } + + Box( + modifier = modifier + .clip(RectangleShape) + .fillMaxSize() + .background(backgroundColor) + // Tap gesture detector (like Flutter's TapGestureRecognizer + DoubleTapGestureRecognizer) + // This runs FIRST and handles taps independently from transform gestures + .pointerInput(initialScale, doubleTapScale, zoomThreshold) { + detectTapGestures( + onDoubleTap = { tapOffset -> + // Double tap: toggle between initial and 2x scale (like Immich's nextScaleState) + scope.launch { + if (scale.value > zoomThreshold) { + // Zoomed in → reset to initial (like Immich's PhotoViewScaleState.initial) + launch { scale.animateTo(initialScale, tween(300)) } + launch { offset.animateTo(Offset.Zero, tween(300)) } + } else { + // Not zoomed → zoom to 2x centered on tap point + val targetScale = doubleTapScale.coerceAtMost(maxScale) + // Calculate offset to zoom toward tap point + val centerX = size.width / 2f + val centerY = size.height / 2f + val focusX = tapOffset.x - centerX + val focusY = tapOffset.y - centerY + val scaleDelta = targetScale / scale.value + val newOffsetX = offset.value.x - focusX * (scaleDelta - 1) + val newOffsetY = offset.value.y - focusY * (scaleDelta - 1) + + launch { scale.animateTo(targetScale, tween(300)) } + launch { offset.animateTo(Offset(newOffsetX, newOffsetY), tween(300)) } + } + } + }, + onTap = { + // Single tap: toggle UI visibility + onTap() + } + ) + } + // Transform gesture detector (pinch zoom, pan, vertical swipe) + .pointerInput(initialScale, zoomThreshold, onSwipeUp, onSwipeDown, onDragProgress) { + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false) + var verticalSwipeStarted = false + var accumulatedVerticalDrag = 0f + + do { + val event = awaitPointerEvent() + val zoom = event.calculateZoom() + val pan = event.calculatePan() + + val isPinch = event.changes.size >= 2 + val newScale = scale.value * zoom + val isZoomed = scale.value > zoomThreshold + + if (isPinch) { + // Pinch to zoom + scope.launch { + scale.snapTo(newScale.coerceIn(minScale, maxScale)) + offset.snapTo( + Offset( + offset.value.x + pan.x * zoom, + offset.value.y + pan.y * zoom + ) + ) + } + event.changes.forEach { if (it.positionChanged()) it.consume() } + } else if (!isZoomed && (onSwipeUp != null || onSwipeDown != null)) { + // Not zoomed - handle vertical swipes for dismiss/info + val isVerticalDrag = kotlin.math.abs(pan.y) > kotlin.math.abs(pan.x) * 1.5f + + if (isVerticalDrag) { + accumulatedVerticalDrag += pan.y + } + + val significantVerticalMovement = kotlin.math.abs(accumulatedVerticalDrag) > 30f + if (isVerticalDrag && significantVerticalMovement) { + verticalSwipeStarted = true + } + + if (verticalSwipeStarted) { + if (accumulatedVerticalDrag > 0) { + val dragProgress = (accumulatedVerticalDrag / maxDragDistance).coerceIn(0f, 1f) + onDragProgress?.invoke(1f - dragProgress) + scope.launch { verticalDragOffset.snapTo(accumulatedVerticalDrag) } + } + event.changes.forEach { if (it.positionChanged()) it.consume() } + } + } else if (isZoomed) { + // Zoomed - handle panning + scope.launch { + offset.snapTo( + Offset( + offset.value.x + pan.x, + offset.value.y + pan.y + ) + ) + } + event.changes.forEach { if (it.positionChanged()) it.consume() } + } + } while (event.changes.any { it.pressed }) + + // Handle vertical swipe completion + if (verticalSwipeStarted) { + when { + accumulatedVerticalDrag > swipeThreshold -> onSwipeDown?.invoke() + accumulatedVerticalDrag < -swipeThreshold -> { + onSwipeUp?.invoke() + scope.launch { verticalDragOffset.snapTo(0f) } + } + else -> { + scope.launch { + verticalDragOffset.animateTo(0f, tween(200)) + onDragProgress?.invoke(1f) + } + } + } + } + } + }, + ) { + val effectiveMinScale = maxOf(minScale, initialScale) + val currentScale = scale.value + + // Calculate swipe-to-dismiss visual effects + val swipeDragProgress = (verticalDragOffset.value.coerceAtLeast(0f) / maxDragDistance).coerceIn(0f, 1f) + val swipeScale = 1f - (swipeDragProgress * 0.3f) + + val imageModifier = Modifier + .align(Alignment.Center) + .graphicsLayer( + scaleX = maxOf(effectiveMinScale, minOf(maxScale, currentScale)) * swipeScale, + scaleY = maxOf(effectiveMinScale, minOf(maxScale, currentScale)) * swipeScale, + translationX = offset.value.x, + translationY = offset.value.y + verticalDragOffset.value.coerceAtLeast(0f), + clip = hideImage, // Clip blur to image bounds + ) + .then( + // BlurEffect only works on Android 12+ (API 31) + if (!hideImage) { + Modifier + } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + Modifier.graphicsLayer { + renderEffect = BlurEffect( + radiusX = hideBlurRadius, + radiusY = hideBlurRadius, + edgeTreatment = TileMode.Decal, // Don't extend blur beyond image edges + ) + } + } else { + // For Android < 12, make image nearly invisible (overlay will cover it) + Modifier.graphicsLayer { alpha = 0.05f } + } + ) + + when (source) { + is ZoomableImageSource.Bmp -> Image( + modifier = imageModifier, + contentDescription = null, + bitmap = source.bitmap.asImageBitmap(), + ) + + is ZoomableImageSource.Resource -> Image( + modifier = imageModifier, + contentDescription = null, + painter = painterResource(id = source.resId), + ) + } + + // Overlay for Android < 12 when hiding image (fallback for no blur support) + if (hideImage && android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.92f)), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(48.dp), + imageVector = Icons.Default.VisibilityOff, + contentDescription = "hidden", + tint = Color.White.copy(alpha = 0.5f), + ) + } + } + } +} + +private fun calculateInitialScale( + source: ZoomableImageSource, + screenWidthDp: Int, + density: Float, +): Float { + if (source is ZoomableImageSource.Bmp) { + val screenWidthPx = screenWidthDp * density + return screenWidthPx / source.bitmap.width.toFloat() + } + return 1f +} + +@Preview +@Composable +private fun ZoomableImagePreview() { + ZoomableImage( + modifier = Modifier.fillMaxSize(), + source = ZoomableImageSource.Resource(PresentationR.drawable.ic_gallery) + ) +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/ADetailerSection.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/ADetailerSection.kt new file mode 100644 index 000000000..94507a143 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/ADetailerSection.kt @@ -0,0 +1,97 @@ +package dev.minios.pdaiv1.presentation.widget.input + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.ADetailerConfig +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +/** + * Composable for configuring ADetailer extension settings. + * ADetailer automatically detects faces/hands and refines them. + */ +@Composable +fun ADetailerSection( + modifier: Modifier = Modifier, + config: ADetailerConfig, + onConfigChange: (ADetailerConfig) -> Unit, +) { + Column(modifier = modifier) { + // Enable/Disable toggle + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(id = LocalizationR.string.hint_adetailer_enabled), + style = MaterialTheme.typography.bodyLarge, + ) + Switch( + checked = config.enabled, + onCheckedChange = { onConfigChange(config.copy(enabled = it)) }, + ) + } + + // Expanded settings when enabled + AnimatedVisibility(visible = config.enabled) { + Column(modifier = Modifier.padding(top = 8.dp)) { + // Model selection + DropdownTextField( + modifier = Modifier.fillMaxWidth(), + label = LocalizationR.string.hint_adetailer_model.asUiText(), + value = config.model, + items = ADetailerConfig.AVAILABLE_MODELS, + onItemSelected = { onConfigChange(config.copy(model = it)) }, + displayDelegate = { it.asUiText() }, + ) + + // Confidence slider + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + id = LocalizationR.string.hint_adetailer_confidence, + config.confidence.roundTo(2).toString() + ), + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = config.confidence, + onValueChange = { onConfigChange(config.copy(confidence = it.roundTo(2))) }, + valueRange = 0.1f..1.0f, + colors = sliderColors, + ) + + // Denoising strength slider + Text( + text = stringResource( + id = LocalizationR.string.hint_adetailer_denoising, + config.denoisingStrength.roundTo(2).toString() + ), + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = config.denoisingStrength, + onValueChange = { onConfigChange(config.copy(denoisingStrength = it.roundTo(2))) }, + valueRange = 0.0f..1.0f, + colors = sliderColors, + ) + } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/DropdownTextField.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/DropdownTextField.kt similarity index 90% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/DropdownTextField.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/DropdownTextField.kt index 708b7c1fb..6b3ffe0fe 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/DropdownTextField.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/DropdownTextField.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.widget.input +package dev.minios.pdaiv1.presentation.widget.input import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -22,11 +22,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.extensions.shimmer -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.core.extensions.shimmer +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.theme.textFieldColors @Composable fun DropdownTextField( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/GenerationInputForm.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/GenerationInputForm.kt new file mode 100644 index 000000000..58a2c19b5 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/GenerationInputForm.kt @@ -0,0 +1,858 @@ +package dev.minios.pdaiv1.presentation.widget.input + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.ArrowDropUp +import androidx.compose.material.icons.filled.AspectRatio +import androidx.compose.material.icons.filled.Casino +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.filled.SwapHoriz +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.ADetailerConfig +import dev.minios.pdaiv1.domain.entity.ForgeModule +import dev.minios.pdaiv1.domain.entity.ModelType +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.OpenAiQuality +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.OpenAiStyle +import dev.minios.pdaiv1.domain.entity.Scheduler +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.StabilityAiClipGuidance +import dev.minios.pdaiv1.domain.entity.StabilityAiSampler +import dev.minios.pdaiv1.domain.entity.StabilityAiStylePreset +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.core.GenerationMviState +import dev.minios.pdaiv1.presentation.model.AspectRatio +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.model.QnnResolution +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.utils.Constants +import dev.minios.pdaiv1.presentation.utils.Constants.BATCH_RANGE_MAX +import dev.minios.pdaiv1.presentation.utils.Constants.BATCH_RANGE_MIN +import dev.minios.pdaiv1.presentation.utils.Constants.CFG_SCALE_RANGE_MAX +import dev.minios.pdaiv1.presentation.utils.Constants.CFG_SCALE_RANGE_MIN +import dev.minios.pdaiv1.presentation.utils.Constants.SAMPLING_STEPS_LOCAL_DIFFUSION_MAX +import dev.minios.pdaiv1.presentation.utils.Constants.SAMPLING_STEPS_RANGE_MAX +import dev.minios.pdaiv1.presentation.utils.Constants.SAMPLING_STEPS_RANGE_MIN +import dev.minios.pdaiv1.presentation.utils.Constants.SAMPLING_STEPS_RANGE_STABILITY_AI_MAX +import dev.minios.pdaiv1.presentation.utils.Constants.SUB_SEED_STRENGTH_MAX +import dev.minios.pdaiv1.presentation.utils.Constants.SUB_SEED_STRENGTH_MIN +import dev.minios.pdaiv1.presentation.widget.engine.EngineSelectionComponent +import dev.minios.pdaiv1.presentation.widget.engine.QnnRuntimeSelectionComponent +import dev.minios.pdaiv1.presentation.widget.input.chip.ChipTextFieldEvent +import dev.minios.pdaiv1.presentation.widget.input.chip.ChipTextFieldWithItem +import kotlin.math.abs +import kotlin.math.absoluteValue +import kotlin.math.roundToInt +import kotlin.random.Random +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun GenerationInputForm( + modifier: Modifier = Modifier, + state: GenerationMviState, + isImg2Img: Boolean = false, + promptChipTextFieldState: MutableState, + negativePromptChipTextFieldState: MutableState, + processIntent: (GenerationMviIntent) -> Unit = {}, + afterSlidersSection: @Composable () -> Unit = {}, +) { + @Composable + fun batchComponent() { + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + id = LocalizationR.string.hint_batch, + "${state.batchCount}", + ), + ) + SliderTextInputField( + value = state.batchCount * 1f, + valueRange = (BATCH_RANGE_MIN * 1f)..(BATCH_RANGE_MAX * 1f), + valueDiff = 1f, + fractionDigits = 0, + steps = abs(BATCH_RANGE_MIN - BATCH_RANGE_MAX) - 1, + sliderColors = sliderColors, + onValueChange = { processIntent(GenerationMviIntent.Update.Batch(it.roundToInt())) }, + ) + } + + @Composable + fun RowScope.sizeTextFieldsComponent(modifier: Modifier = Modifier) { + TextField( + modifier = modifier.padding(end = 4.dp), + value = state.width, + onValueChange = { value -> + if (value.length <= 4) { + value + .filter { it.isDigit() } + .let(GenerationMviIntent.Update.Size::Width) + .let(processIntent::invoke) + } + }, + isError = state.widthValidationError != null, + supportingText = { + state.widthValidationError?.let { + Text( + it.asString(), + color = MaterialTheme.colorScheme.error + ) + } + }, + label = { Text(stringResource(id = LocalizationR.string.width)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + colors = textFieldColors, + ) + TextField( + modifier = modifier.padding(start = 4.dp), + value = state.height, + onValueChange = { value -> + if (value.length <= 4) { + value + .filter { it.isDigit() } + .let(GenerationMviIntent.Update.Size::Height) + .let(processIntent::invoke) + } + }, + isError = state.heightValidationError != null, + supportingText = { + state.heightValidationError?.let { + Text( + it.asString(), + color = MaterialTheme.colorScheme.error + ) + } + }, + label = { Text(stringResource(id = LocalizationR.string.height)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + colors = textFieldColors, + ) + } + + @Composable + fun sizeButtonsComponent() { + var aspectRatioMenuExpanded by remember { mutableStateOf(false) } + + IconButton( + onClick = { processIntent(GenerationMviIntent.Update.Size.Swap) }, + ) { + Icon( + imageVector = Icons.Default.SwapHoriz, + contentDescription = stringResource(id = LocalizationR.string.swap_dimensions), + ) + } + + Box { + IconButton( + onClick = { aspectRatioMenuExpanded = true }, + ) { + Icon( + imageVector = Icons.Default.AspectRatio, + contentDescription = stringResource(id = LocalizationR.string.aspect_ratio), + ) + } + DropdownMenu( + expanded = aspectRatioMenuExpanded, + onDismissRequest = { aspectRatioMenuExpanded = false }, + containerColor = MaterialTheme.colorScheme.surface, + ) { + AspectRatio.entries.forEach { ratio -> + DropdownMenuItem( + text = { Text(ratio.label) }, + onClick = { + aspectRatioMenuExpanded = false + processIntent(GenerationMviIntent.Update.Size.AspectRatio(ratio)) + }, + ) + } + } + } + } + + Column(modifier = modifier) { + if (!state.onBoardingDemo) { + when (state.mode) { + ServerSource.AUTOMATIC1111, + ServerSource.SWARM_UI, + ServerSource.STABILITY_AI, + ServerSource.HUGGING_FACE, + ServerSource.LOCAL_MICROSOFT_ONNX, + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE, + ServerSource.LOCAL_QUALCOMM_QNN -> EngineSelectionComponent( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) + + ServerSource.OPEN_AI -> DropdownTextField( + modifier = Modifier.padding(top = 8.dp), + label = LocalizationR.string.hint_model_open_ai.asUiText(), + value = state.openAiModel, + items = OpenAiModel.entries, + onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Model(it)) }, + ) + + else -> Unit + } + // Model type selection (SD/SDXL/Flux) for A1111 + if (state.mode == ServerSource.AUTOMATIC1111) { + DropdownTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + label = LocalizationR.string.hint_model_type.asUiText(), + value = state.modelType, + items = ModelType.entries, + onItemSelected = { processIntent(GenerationMviIntent.Update.ModelTypeChange(it)) }, + displayDelegate = { it.displayName.asUiText() }, + ) + // VAE / Text Encoder multi-select (Forge only) + if (state.availableForgeModules.isNotEmpty()) { + MultiSelectDropdownField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + label = LocalizationR.string.hint_vae_text_encoder.asUiText(), + selectedItems = state.selectedForgeModules, + availableItems = state.availableForgeModules, + onSelectionChanged = { modules -> + processIntent(GenerationMviIntent.Update.ForgeModules(modules)) + }, + displayDelegate = { it.name.asUiText() }, + ) + } + } + } + if (state.formPromptTaggedInput) { + ChipTextFieldWithItem( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + textFieldValueState = promptChipTextFieldState, + label = LocalizationR.string.hint_prompt, + list = state.promptKeywords, + onItemClick = { _, tag -> + processIntent( + GenerationMviIntent.SetModal( + Modal.EditTag( + prompt = state.prompt, + negativePrompt = state.negativePrompt, + tag = tag, + isNegative = false, + ) + ) + ) + }, + ) { event -> + val prompt = processTaggedPrompt(state.promptKeywords, event) + processIntent(GenerationMviIntent.Update.Prompt(prompt)) + } + } else { + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + value = state.prompt, + onValueChange = { processIntent(GenerationMviIntent.Update.Prompt(it)) }, + label = { Text(stringResource(id = LocalizationR.string.hint_prompt)) }, + colors = textFieldColors, + ) + } + + // Horde does not support "negative prompt", Flux models also don't support it + // Show negative prompt only for non-Flux model types + val showNegativePrompt = when (state.mode) { + ServerSource.AUTOMATIC1111 -> state.modelType != ModelType.FLUX + ServerSource.SWARM_UI, + ServerSource.HUGGING_FACE, + ServerSource.STABILITY_AI, + ServerSource.LOCAL_MICROSOFT_ONNX, + ServerSource.LOCAL_QUALCOMM_QNN -> true + else -> false + } + if (showNegativePrompt) { + if (state.formPromptTaggedInput) { + ChipTextFieldWithItem( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + textFieldValueState = negativePromptChipTextFieldState, + label = LocalizationR.string.hint_prompt_negative, + list = state.negativePromptKeywords, + onItemClick = { _, tag -> + processIntent( + GenerationMviIntent.SetModal( + Modal.EditTag( + prompt = state.prompt, + negativePrompt = state.negativePrompt, + tag = tag, + isNegative = true, + ) + ) + ) + }, + ) { event -> + val prompt = processTaggedPrompt(state.negativePromptKeywords, event) + processIntent(GenerationMviIntent.Update.NegativePrompt(prompt)) + } + } else { + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + value = state.negativePrompt, + onValueChange = { processIntent(GenerationMviIntent.Update.NegativePrompt(it)) }, + label = { Text(stringResource(id = LocalizationR.string.hint_prompt_negative)) }, + colors = textFieldColors, + ) + } + } + + // Size input fields with control buttons + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + when (state.mode) { + ServerSource.HORDE, + ServerSource.LOCAL_MICROSOFT_ONNX -> { + DropdownTextField( + modifier = Modifier.weight(1f).padding(end = 4.dp), + label = LocalizationR.string.width.asUiText(), + value = state.width, + items = Constants.sizes, + onItemSelected = { processIntent(GenerationMviIntent.Update.Size.Width(it)) }, + ) + DropdownTextField( + modifier = Modifier.weight(1f).padding(start = 4.dp), + label = LocalizationR.string.height.asUiText(), + value = state.height, + items = Constants.sizes, + onItemSelected = { processIntent(GenerationMviIntent.Update.Size.Height(it)) }, + ) + } + + ServerSource.AUTOMATIC1111, + ServerSource.SWARM_UI, + ServerSource.HUGGING_FACE -> { + sizeTextFieldsComponent(Modifier.weight(1f)) + sizeButtonsComponent() + } + + ServerSource.STABILITY_AI -> { + if (!isImg2Img) { + sizeTextFieldsComponent(Modifier.weight(1f)) + sizeButtonsComponent() + } + } + + ServerSource.OPEN_AI -> { + DropdownTextField( + label = LocalizationR.string.hint_image_size.asUiText(), + value = state.openAiSize, + items = OpenAiSize.entries.filter { + it.supportedModels.contains(state.openAiModel) + }, + onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Size(it)) }, + displayDelegate = { it.key.asUiText() }, + ) + } + + ServerSource.LOCAL_QUALCOMM_QNN -> { + // For img2img in CPU/GPU mode, limit to 512x512 for memory stability + val availableResolutions = if (isImg2Img) { + QnnResolution.forImg2Img(state.qnnRunOnCpu) + } else { + QnnResolution.forModelType(state.qnnRunOnCpu) + } + val defaultResolution = if (isImg2Img && state.qnnRunOnCpu) { + QnnResolution.RES_512x512 + } else { + QnnResolution.defaultForModelType(state.qnnRunOnCpu) + } + val currentResolution = QnnResolution.fromDimensions( + state.width.toIntOrNull() ?: defaultResolution.width, + state.height.toIntOrNull() ?: defaultResolution.height + )?.takeIf { it in availableResolutions } ?: defaultResolution + DropdownTextField( + modifier = Modifier.fillMaxWidth(), + label = LocalizationR.string.hint_image_size.asUiText(), + value = currentResolution, + items = availableResolutions, + onItemSelected = { processIntent(GenerationMviIntent.Update.Qnn.Resolution(it)) }, + displayDelegate = { it.displayName.asUiText() }, + ) + } + + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE, + ServerSource.FAL_AI -> Unit + } + } + + if (state.mode == ServerSource.OPEN_AI) { + if (state.openAiModel == OpenAiModel.DALL_E_3) { + DropdownTextField( + modifier = Modifier.padding(top = 8.dp), + label = LocalizationR.string.hint_quality.asUiText(), + value = state.openAiQuality, + items = OpenAiQuality.entries, + onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Quality(it)) }, + ) + DropdownTextField( + modifier = Modifier.padding(top = 8.dp), + label = LocalizationR.string.hint_style.asUiText(), + value = state.openAiStyle, + items = OpenAiStyle.entries, + onItemSelected = { processIntent(GenerationMviIntent.Update.OpenAi.Style(it)) }, + ) + } + batchComponent() + } + + if (state.advancedToggleButtonVisible && state.mode != ServerSource.OPEN_AI) { + TextButton( + modifier = Modifier.align(Alignment.CenterHorizontally), + onClick = { + processIntent( + GenerationMviIntent.SetAdvancedOptionsVisibility(!state.advancedOptionsVisible) + ) + }, + ) { + Icon( + imageVector = if (state.advancedOptionsVisible) Icons.Default.ArrowDropUp + else Icons.Default.ArrowDropDown, + contentDescription = null, + ) + Text( + text = stringResource( + id = if (state.advancedOptionsVisible) LocalizationR.string.action_options_hide + else LocalizationR.string.action_options_show + ) + ) + } + } + + AnimatedVisibility( + visible = state.advancedOptionsVisible && state.mode != ServerSource.OPEN_AI, + ) { + Column(modifier = Modifier.fillMaxWidth()) { + // Sampler selection only supported for A1111, STABILITY AI, QNN + when (state.mode) { + ServerSource.STABILITY_AI, + ServerSource.AUTOMATIC1111, + ServerSource.LOCAL_QUALCOMM_QNN -> DropdownTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + label = LocalizationR.string.hint_sampler.asUiText(), + value = state.selectedSampler, + items = state.availableSamplers, + onItemSelected = { processIntent(GenerationMviIntent.Update.Sampler(it)) }, + displayDelegate = { value -> + if (value == StabilityAiSampler.NONE.toString()) { + LocalizationR.string.hint_autodetect.asUiText() + } else { + value.asUiText() + } + } + ) + + else -> Unit + } + + // Runtime selection for QNN CPU models (CPU/GPU via OpenCL) + if (state.mode == ServerSource.LOCAL_QUALCOMM_QNN && state.qnnRunOnCpu) { + QnnRuntimeSelectionComponent( + modifier = Modifier.padding(top = 8.dp) + ) + } + + // Scheduler selection only for A1111 (Flux models need specific schedulers) + if (state.mode == ServerSource.AUTOMATIC1111) { + DropdownTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + label = LocalizationR.string.hint_scheduler.asUiText(), + value = state.selectedScheduler, + items = Scheduler.entries, + onItemSelected = { processIntent(GenerationMviIntent.Update.Scheduler(it)) }, + displayDelegate = { value -> value.displayName.asUiText() }, + ) + } + + // Style-preset only for Stablity AI + if (state.mode == ServerSource.STABILITY_AI) { + DropdownTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + label = LocalizationR.string.hint_style_preset.asUiText(), + value = state.selectedStylePreset, + items = StabilityAiStylePreset.entries, + onItemSelected = { processIntent(GenerationMviIntent.Update.StabilityAi.Style(it)) }, + displayDelegate = { value -> + if (value == StabilityAiStylePreset.NONE) { + LocalizationR.string.hint_autodetect.asUiText() + } else { + value.key.asUiText() + } + }, + ) + DropdownTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + label = LocalizationR.string.hint_clip_guidance_preset.asUiText(), + value = state.selectedClipGuidancePreset, + items = StabilityAiClipGuidance.entries, + onItemSelected = { processIntent(GenerationMviIntent.Update.StabilityAi.ClipGuidance(it)) }, + ) + } + + // Seed is not available for Hugging Face + if (state.mode != ServerSource.OPEN_AI) { + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + value = state.seed, + onValueChange = { value -> + value + .filter { it.isDigit() } + .let(GenerationMviIntent.Update::Seed) + .let(processIntent::invoke) + }, + label = { Text(stringResource(id = LocalizationR.string.hint_seed)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + trailingIcon = { + Row { + if (state.seed.isNotEmpty()) { + IconButton(onClick = { + processIntent(GenerationMviIntent.Update.Seed("")) + }) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = "Clear", + ) + } + } + IconButton(onClick = { + processIntent(GenerationMviIntent.Update.Seed("${Random.nextLong().absoluteValue}")) + }) { + Icon( + imageVector = Icons.Default.Casino, + contentDescription = "Random", + ) + } + } + }, + colors = textFieldColors, + ) + } + // NSFW flag specifically for Horde API + if (state.mode == ServerSource.HORDE) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Switch( + checked = state.nsfw, + onCheckedChange = { processIntent(GenerationMviIntent.Update.Nsfw(it)) }, + ) + Text( + modifier = Modifier.padding(horizontal = 8.dp), + text = stringResource(id = LocalizationR.string.hint_nsfw), + ) + } + } + // Variation seed supported for A1111, SwarmUI + when (state.mode) { + ServerSource.AUTOMATIC1111, + ServerSource.SWARM_UI -> TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + value = state.subSeed, + onValueChange = { value -> + value + .filter { it.isDigit() } + .let(GenerationMviIntent.Update::SubSeed) + .let(processIntent::invoke) + }, + label = { Text(stringResource(id = LocalizationR.string.hint_sub_seed)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + trailingIcon = { + Row { + if (state.subSeed.isNotEmpty()) { + IconButton(onClick = { + processIntent(GenerationMviIntent.Update.SubSeed("")) + }) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = "Clear", + ) + } + } + IconButton(onClick = { + processIntent(GenerationMviIntent.Update.SubSeed("${Random.nextLong().absoluteValue}")) + }) { + Icon( + imageVector = Icons.Default.Casino, + contentDescription = "Random", + ) + } + } + }, + colors = textFieldColors, + ) + + else -> Unit + } + // Sub-seed strength is not available for Local Diffusion + when (state.mode) { + ServerSource.AUTOMATIC1111, + ServerSource.SWARM_UI, + ServerSource.HORDE -> { + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + id = LocalizationR.string.hint_sub_seed_strength, + "${state.subSeedStrength.roundTo(2)}", + ), + ) + SliderTextInputField( + value = state.subSeedStrength, + valueRange = SUB_SEED_STRENGTH_MIN..SUB_SEED_STRENGTH_MAX, + valueDiff = 0.01f, + sliderColors = sliderColors, + onValueChange = { + processIntent(GenerationMviIntent.Update.SubSeedStrength(it)) + }, + ) + } + + else -> Unit + } + + //Steps not available for open ai + if (state.mode != ServerSource.OPEN_AI) { + val stepsMax = when (state.mode) { + ServerSource.LOCAL_MICROSOFT_ONNX -> SAMPLING_STEPS_LOCAL_DIFFUSION_MAX + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> SAMPLING_STEPS_LOCAL_DIFFUSION_MAX + ServerSource.STABILITY_AI -> SAMPLING_STEPS_RANGE_STABILITY_AI_MAX + else -> SAMPLING_STEPS_RANGE_MAX + } + val steps = state.samplingSteps.coerceIn(SAMPLING_STEPS_RANGE_MIN, stepsMax) + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource(id = LocalizationR.string.hint_sampling_steps, "$steps"), + ) + SliderTextInputField( + value = steps * 1f, + valueRange = (SAMPLING_STEPS_RANGE_MIN * 1f)..(stepsMax * 1f), + valueDiff = 1f, + steps = abs(stepsMax - SAMPLING_STEPS_RANGE_MIN) - 1, + sliderColors = sliderColors, + fractionDigits = 0, + onValueChange = { + processIntent(GenerationMviIntent.Update.SamplingSteps(it.roundToInt())) + }, + ) + } + + // CFG scale not available on open ai and google media pipe + // For Flux models, show Distilled CFG Scale instead + when (state.mode) { + ServerSource.OPEN_AI, + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> Unit + ServerSource.AUTOMATIC1111 -> { + // CFG Scale for all model types + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + LocalizationR.string.hint_cfg_scale, + "${state.cfgScale.roundTo(2)}", + ), + ) + SliderTextInputField( + value = state.cfgScale, + valueRange = (CFG_SCALE_RANGE_MIN * 1f)..(CFG_SCALE_RANGE_MAX * 1f), + valueDiff = 0.5f, + steps = abs(CFG_SCALE_RANGE_MAX - CFG_SCALE_RANGE_MIN) * 2 - 1, + sliderColors = sliderColors, + onValueChange = { + processIntent(GenerationMviIntent.Update.CfgScale(it)) + }, + ) + // Flux also needs Distilled CFG Scale + if (state.modelType == ModelType.FLUX) { + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + LocalizationR.string.hint_distilled_cfg_scale, + "${state.distilledCfgScale.roundTo(2)}", + ), + ) + SliderTextInputField( + value = state.distilledCfgScale, + valueRange = 1f..10f, + valueDiff = 0.5f, + steps = 17, + sliderColors = sliderColors, + onValueChange = { + processIntent(GenerationMviIntent.Update.DistilledCfgScale(it)) + }, + ) + } + } + else -> { + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + LocalizationR.string.hint_cfg_scale, + "${state.cfgScale.roundTo(2)}", + ), + ) + SliderTextInputField( + value = state.cfgScale, + valueRange = (CFG_SCALE_RANGE_MIN * 1f)..(CFG_SCALE_RANGE_MAX * 1f), + valueDiff = 0.5f, + steps = abs(CFG_SCALE_RANGE_MAX - CFG_SCALE_RANGE_MIN) * 2 - 1, + sliderColors = sliderColors, + onValueChange = { + processIntent(GenerationMviIntent.Update.CfgScale(it)) + }, + ) + } + } + + when (state.mode) { + ServerSource.AUTOMATIC1111, + ServerSource.SWARM_UI, + ServerSource.STABILITY_AI, + ServerSource.HORDE, + ServerSource.LOCAL_QUALCOMM_QNN -> afterSlidersSection() + + else -> Unit + } + + // Batch is not available for any Local + when (state.mode) { + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE, ServerSource.LOCAL_MICROSOFT_ONNX -> Unit + else -> batchComponent() + } + // Hires, ADetailer, Restore faces - only for A1111 + if (state.mode == ServerSource.AUTOMATIC1111) { + HiresSection( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + config = state.hiresConfig, + onConfigChange = { processIntent(GenerationMviIntent.Update.Hires(it)) }, + ) + ADetailerSection( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + config = state.aDetailerConfig, + onConfigChange = { processIntent(GenerationMviIntent.Update.ADetailer(it)) }, + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(id = LocalizationR.string.hint_restore_faces), + style = MaterialTheme.typography.bodyLarge, + ) + Switch( + checked = state.restoreFaces, + onCheckedChange = { + processIntent(GenerationMviIntent.Update.RestoreFaces(it)) + }, + ) + } + } + // QNN Hires.Fix - only for NPU (not CPU/GPU) and only for txt2img + if (state.mode == ServerSource.LOCAL_QUALCOMM_QNN && !state.qnnRunOnCpu && !isImg2Img) { + val currentResolution = QnnResolution.fromDimensions( + state.width.toIntOrNull() ?: 512, + state.height.toIntOrNull() ?: 512 + ) ?: QnnResolution.DEFAULT + + // Only show if Hires is available for this resolution (has targets with same aspect ratio) + if (QnnResolution.isHiresAvailable(currentResolution, runOnCpu = false)) { + QnnHiresSection( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + baseResolution = currentResolution, + config = state.qnnHiresConfig, + onConfigChange = { processIntent(GenerationMviIntent.Update.Qnn.Hires(it)) }, + ) + } + } + } + } + } +} + +private fun processTaggedPrompt(keywords: List, event: ChipTextFieldEvent): String { + val newKeywords = when (event) { + is ChipTextFieldEvent.Add -> buildList { + addAll(keywords) + add(event.item) + } + + is ChipTextFieldEvent.AddBatch -> buildList { + addAll(keywords) + addAll(event.items) + } + + is ChipTextFieldEvent.Remove -> keywords.filterIndexed { i, _ -> i != event.index } + is ChipTextFieldEvent.Update -> keywords.mapIndexed { i, s -> if (i == event.index) event.item else s } + } + return newKeywords.joinToString(", ") +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/HiresSection.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/HiresSection.kt new file mode 100644 index 000000000..f5a029ac5 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/HiresSection.kt @@ -0,0 +1,113 @@ +package dev.minios.pdaiv1.presentation.widget.input + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.HiresConfig +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +/** + * Composable for configuring Hires. Fix settings. + * Hires. Fix upscales and refines images using a second pass. + */ +@Composable +fun HiresSection( + modifier: Modifier = Modifier, + config: HiresConfig, + onConfigChange: (HiresConfig) -> Unit, +) { + Column(modifier = modifier) { + // Enable/Disable toggle + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(id = LocalizationR.string.hint_hires_enabled), + style = MaterialTheme.typography.bodyLarge, + ) + Switch( + checked = config.enabled, + onCheckedChange = { onConfigChange(config.copy(enabled = it)) }, + ) + } + + // Expanded settings when enabled + AnimatedVisibility(visible = config.enabled) { + Column(modifier = Modifier.padding(top = 8.dp)) { + // Upscaler selection + DropdownTextField( + modifier = Modifier.fillMaxWidth(), + label = LocalizationR.string.hint_hires_upscaler.asUiText(), + value = config.upscaler, + items = HiresConfig.AVAILABLE_UPSCALERS, + onItemSelected = { onConfigChange(config.copy(upscaler = it)) }, + displayDelegate = { it.asUiText() }, + ) + + // Scale slider (1.0 - 4.0) + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + id = LocalizationR.string.hint_hires_scale, + config.scale.roundTo(1).toString() + ), + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = config.scale, + onValueChange = { onConfigChange(config.copy(scale = it.roundTo(1))) }, + valueRange = 1.0f..4.0f, + colors = sliderColors, + ) + + // Steps slider (0 = use same as first pass, 1-150) + Text( + text = stringResource( + id = LocalizationR.string.hint_hires_steps, + config.steps.toString() + ), + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = config.steps.toFloat(), + onValueChange = { onConfigChange(config.copy(steps = it.toInt())) }, + valueRange = 0f..150f, + steps = 150, + colors = sliderColors, + ) + + // Denoising strength slider (0.0 - 1.0) + Text( + text = stringResource( + id = LocalizationR.string.hint_hires_denoising, + config.denoisingStrength.roundTo(2).toString() + ), + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = config.denoisingStrength, + onValueChange = { onConfigChange(config.copy(denoisingStrength = it.roundTo(2))) }, + valueRange = 0.0f..1.0f, + colors = sliderColors, + ) + } + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/MultiSelectDropdownField.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/MultiSelectDropdownField.kt new file mode 100644 index 000000000..c26e54a4d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/MultiSelectDropdownField.kt @@ -0,0 +1,107 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) + +package dev.minios.pdaiv1.presentation.widget.input + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.InputChip +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuAnchorType +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.presentation.theme.textFieldColors + +@Composable +fun MultiSelectDropdownField( + modifier: Modifier = Modifier, + label: UiText = UiText.empty, + selectedItems: List = emptyList(), + availableItems: List = emptyList(), + onSelectionChanged: (List) -> Unit = {}, + displayDelegate: (T) -> UiText = { t -> t.toString().asUiText() }, +) { + var expanded by remember { mutableStateOf(false) } + val unselectedItems = availableItems.filter { it !in selectedItems } + + Column(modifier = modifier) { + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + ) { + TextField( + modifier = Modifier + .fillMaxWidth() + .menuAnchor(MenuAnchorType.PrimaryNotEditable), + value = if (selectedItems.isEmpty()) "" else "${selectedItems.size} selected", + onValueChange = {}, + readOnly = true, + label = { Text(label.asString()) }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) + }, + colors = textFieldColors, + ) + + if (unselectedItems.isNotEmpty()) { + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + containerColor = MaterialTheme.colorScheme.background, + ) { + unselectedItems.forEach { item -> + DropdownMenuItem( + text = { Text(displayDelegate(item).asString()) }, + onClick = { + onSelectionChanged(selectedItems + item) + }, + ) + } + } + } + } + + if (selectedItems.isNotEmpty()) { + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + ) { + selectedItems.forEach { item -> + InputChip( + modifier = Modifier.padding(end = 4.dp), + selected = false, + onClick = { onSelectionChanged(selectedItems - item) }, + label = { Text(displayDelegate(item).asString()) }, + trailingIcon = { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Remove", + ) + }, + ) + } + } + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/QnnHiresSection.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/QnnHiresSection.kt new file mode 100644 index 000000000..1395c07cb --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/QnnHiresSection.kt @@ -0,0 +1,133 @@ +package dev.minios.pdaiv1.presentation.widget.input + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.QnnHiresConfig +import dev.minios.pdaiv1.presentation.model.QnnResolution +import dev.minios.pdaiv1.presentation.theme.sliderColors +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +/** + * Composable for configuring QNN Hires.Fix settings (NPU only). + * + * Hires.Fix generates at base resolution, then upscales and refines + * at the target resolution with the same aspect ratio. + * + * Supported upscale paths: + * - 512×512 → 768×768, 1024×1024 + * - 512×768 → 768×1024 + * - 768×512 → 1024×768 + * - 768×768 → 1024×1024 + * + * @param baseResolution Current base resolution selected in the form. + */ +@Composable +fun QnnHiresSection( + modifier: Modifier = Modifier, + baseResolution: QnnResolution, + config: QnnHiresConfig, + onConfigChange: (QnnHiresConfig) -> Unit, +) { + // Get available targets for the current base resolution (same aspect ratio) + val targetResolutions = QnnResolution.hiresTargetResolutions(baseResolution) + + // If no targets available for this base, don't show section + if (targetResolutions.isEmpty()) return + + val currentTarget = QnnResolution.fromDimensions(config.targetWidth, config.targetHeight) + ?.takeIf { it in targetResolutions } + ?: QnnResolution.defaultHiresTarget(baseResolution) + ?: return + + Column(modifier = modifier) { + // Enable/Disable toggle + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column { + Text( + text = stringResource(id = LocalizationR.string.hint_qnn_hires_enabled), + style = MaterialTheme.typography.bodyLarge, + ) + Text( + text = stringResource(id = LocalizationR.string.hint_qnn_hires_description), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Switch( + checked = config.enabled, + onCheckedChange = { onConfigChange(config.copy(enabled = it)) }, + ) + } + + // Expanded settings when enabled + AnimatedVisibility(visible = config.enabled) { + Column(modifier = Modifier.padding(top = 8.dp)) { + // Target resolution selection + DropdownTextField( + modifier = Modifier.fillMaxWidth(), + label = LocalizationR.string.hint_qnn_hires_target.asUiText(), + value = currentTarget, + items = targetResolutions, + onItemSelected = { resolution -> + onConfigChange(config.copy( + targetWidth = resolution.width, + targetHeight = resolution.height + )) + }, + displayDelegate = { it.displayName.asUiText() }, + ) + + // Steps slider (0 = use same as first pass, 1-50) + Text( + modifier = Modifier.padding(top = 8.dp), + text = stringResource( + id = LocalizationR.string.hint_qnn_hires_steps, + config.steps.toString() + ), + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = config.steps.toFloat(), + onValueChange = { onConfigChange(config.copy(steps = it.toInt())) }, + valueRange = 0f..50f, + steps = 50, + colors = sliderColors, + ) + + // Denoising strength slider (0.0 - 1.0) + Text( + text = stringResource( + id = LocalizationR.string.hint_qnn_hires_denoising, + config.denoisingStrength.roundTo(2).toString() + ), + style = MaterialTheme.typography.bodyMedium, + ) + Slider( + value = config.denoisingStrength, + onValueChange = { onConfigChange(config.copy(denoisingStrength = it.roundTo(2))) }, + valueRange = 0.0f..1.0f, + colors = sliderColors, + ) + } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/SliderTextInputField.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/SliderTextInputField.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/SliderTextInputField.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/SliderTextInputField.kt index 243e36441..1d1c2e2d4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/SliderTextInputField.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/SliderTextInputField.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.input +package dev.minios.pdaiv1.presentation.widget.input import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -30,9 +30,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.common.math.roundTo -import com.shifthackz.aisdv1.core.localization.R -import com.shifthackz.aisdv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.core.common.math.roundTo +import dev.minios.pdaiv1.core.localization.R +import dev.minios.pdaiv1.presentation.theme.textFieldColors @Composable fun SliderTextInputField( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextField.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextField.kt similarity index 98% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextField.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextField.kt index f28ad38fc..5ea79e96f 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextField.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextField.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) -package com.shifthackz.aisdv1.presentation.widget.input.chip +package dev.minios.pdaiv1.presentation.widget.input.chip import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -36,7 +36,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.theme.textFieldColors +import dev.minios.pdaiv1.presentation.theme.textFieldColors import org.apache.commons.lang3.StringUtils sealed interface ChipTextFieldEvent { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextFieldItem.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextFieldItem.kt similarity index 93% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextFieldItem.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextFieldItem.kt index 7ffade3bf..b26eaf1a8 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextFieldItem.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextFieldItem.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.input.chip +package dev.minios.pdaiv1.presentation.widget.input.chip import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -18,8 +18,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.aisdv1.presentation.theme.isSdAppInDarkTheme +import dev.minios.pdaiv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.theme.isSdAppInDarkTheme import com.shifthackz.catppuccin.palette.Catppuccin @Composable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextFieldWithItem.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextFieldWithItem.kt similarity index 91% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextFieldWithItem.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextFieldWithItem.kt index d5806c12f..c256e6ebb 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/input/chip/ChipTextFieldWithItem.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/input/chip/ChipTextFieldWithItem.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.input.chip +package dev.minios.pdaiv1.presentation.widget.input.chip import androidx.compose.foundation.layout.Arrangement import androidx.compose.material3.LocalTextStyle @@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.presentation.utils.ExtrasFormatter +import dev.minios.pdaiv1.presentation.utils.ExtrasFormatter @Composable fun ChipTextFieldWithItem( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/GridIcon.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/GridIcon.kt similarity index 87% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/GridIcon.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/GridIcon.kt index 06a004bbb..4082d33fc 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/GridIcon.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/GridIcon.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.item +package dev.minios.pdaiv1.presentation.widget.item import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -14,7 +14,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.domain.entity.Grid +import dev.minios.pdaiv1.domain.entity.Grid @Composable fun GridIcon( @@ -46,6 +46,12 @@ fun GridIcon( } } +@Composable +@Preview +private fun GridIconsPreview1() { + GridIcon(Grid.Fixed1) +} + @Composable @Preview private fun GridIconsPreview2() { @@ -69,3 +75,9 @@ private fun GridIconsPreview4() { private fun GridIconsPreview5() { GridIcon(Grid.Fixed5) } + +@Composable +@Preview +private fun GridIconsPreview6() { + GridIcon(Grid.Fixed6) +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/NavigationItemIcon.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/NavigationItemIcon.kt similarity index 88% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/NavigationItemIcon.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/NavigationItemIcon.kt index 3d213aa6c..b825a10f1 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/NavigationItemIcon.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/NavigationItemIcon.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.item +package dev.minios.pdaiv1.presentation.widget.item import androidx.compose.foundation.Image import androidx.compose.material3.Icon @@ -6,7 +6,7 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource -import com.shifthackz.aisdv1.presentation.model.NavItem +import dev.minios.pdaiv1.presentation.model.NavItem @Composable fun NavigationItemIcon(icon: NavItem.Icon) { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SettingsHeader.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SettingsHeader.kt similarity index 95% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SettingsHeader.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SettingsHeader.kt index 0de235fbc..89d50edd4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SettingsHeader.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SettingsHeader.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.item +package dev.minios.pdaiv1.presentation.widget.item import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility @@ -27,9 +27,9 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.extensions.shimmer -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString +import dev.minios.pdaiv1.core.extensions.shimmer +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString @Composable fun SettingsHeader( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SettingsItem.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SettingsItem.kt similarity index 96% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SettingsItem.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SettingsItem.kt index ba4d3f969..32c72662b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SettingsItem.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SettingsItem.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.item +package dev.minios.pdaiv1.presentation.widget.item import androidx.compose.animation.Animatable import androidx.compose.animation.AnimatedVisibility @@ -36,9 +36,9 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.extensions.shimmer -import com.shifthackz.aisdv1.core.model.UiText -import com.shifthackz.aisdv1.core.model.asString +import dev.minios.pdaiv1.core.extensions.shimmer +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString import kotlinx.coroutines.delay @Composable diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SupporterItem.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SupporterItem.kt similarity index 93% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SupporterItem.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SupporterItem.kt index 903cc0fef..67aac2641 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/item/SupporterItem.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/item/SupporterItem.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.item +package dev.minios.pdaiv1.presentation.widget.item import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -23,8 +23,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.shifthackz.aisdv1.core.common.extensions.format -import com.shifthackz.aisdv1.domain.entity.Supporter +import dev.minios.pdaiv1.core.common.extensions.format +import dev.minios.pdaiv1.domain.entity.Supporter @Composable @Preview diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/scaffold/CollapsibleScaffold.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/scaffold/CollapsibleScaffold.kt new file mode 100644 index 000000000..ec360a5c6 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/scaffold/CollapsibleScaffold.kt @@ -0,0 +1,238 @@ +package dev.minios.pdaiv1.presentation.widget.scaffold + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * A Scaffold-like composable that supports collapsing TopBar on scroll. + * Works like Gallery - content scrolls under TopBar, TopBar slides up to reveal content. + * + * @param topBarContent The content for the top bar + * @param bottomBar Optional bottom bar content + * @param topBarHeight Height of the top bar content (without status bar) + * @param bottomNavBarHeight Height of home navigation bar + * @param bottomToolbarHeight Height of screen's own bottom toolbar + * @param scrollState Optional external scroll state + * @param contentScrollable Content that will be wrapped in a scrollable Column with proper spacing + */ +@Composable +fun CollapsibleScaffold( + topBarContent: @Composable () -> Unit, + modifier: Modifier = Modifier, + bottomBar: @Composable () -> Unit = {}, + topBarHeight: Dp = 72.dp, + bottomNavBarHeight: Dp = 80.dp, + bottomToolbarHeight: Dp = 80.dp, + scrollState: ScrollState? = null, + contentScrollable: @Composable () -> Unit, +) { + val density = LocalDensity.current + val statusBarHeightDp = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val totalTopBarHeight = topBarHeight + statusBarHeightDp + val topBarHeightPx = with(density) { totalTopBarHeight.toPx() } + + // UI offset: 0f = fully visible, 1f = fully hidden + var uiOffset by remember { mutableFloatStateOf(0f) } + + // Animate the offset for smoother transitions + val animatedOffset by animateFloatAsState( + targetValue = uiOffset, + animationSpec = tween(durationMillis = 150), + label = "topbar_offset" + ) + + // NestedScrollConnection to intercept scroll events from any child + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.y + val newOffset = (uiOffset - delta / topBarHeightPx).coerceIn(0f, 1f) + uiOffset = newOffset + return Offset.Zero + } + } + } + + // Internal scroll state for content + val internalScrollState = scrollState ?: rememberScrollState() + + // Reset topbar when at top + LaunchedEffect(internalScrollState) { + snapshotFlow { internalScrollState.value } + .collect { scrollValue -> + if (scrollValue < 50) { + uiOffset = 0f + } + } + } + + val totalBottomPadding = bottomNavBarHeight + bottomToolbarHeight + + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .nestedScroll(nestedScrollConnection) + ) { + // Main content - scrollable Column with internal spacing (like Gallery's contentPadding) + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(internalScrollState) + .padding(bottom = totalBottomPadding) + ) { + // Internal spacer for TopBar (like Gallery's contentPadding top) + Spacer(modifier = Modifier.height(totalTopBarHeight)) + // Actual content + contentScrollable() + } + + // TopBar as overlay - slides up based on scroll + Column( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + .graphicsLayer { + translationY = -topBarHeightPx * animatedOffset + } + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + ) { + topBarContent() + } + + // Bottom bar - positioned above the navigation bar + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = bottomNavBarHeight) + ) { + bottomBar() + } + } +} + +/** + * Overload that accepts PaddingValues for compatibility. + * Content must apply padding itself internally (not as outer container padding). + */ +@Composable +fun CollapsibleScaffold( + topBarContent: @Composable () -> Unit, + modifier: Modifier = Modifier, + bottomBar: @Composable () -> Unit = {}, + topBarHeight: Dp = 72.dp, + bottomNavBarHeight: Dp = 80.dp, + bottomToolbarHeight: Dp = 80.dp, + scrollState: ScrollState? = null, + content: @Composable (PaddingValues) -> Unit, +) { + val density = LocalDensity.current + val statusBarHeightDp = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val totalTopBarHeight = topBarHeight + statusBarHeightDp + val topBarHeightPx = with(density) { totalTopBarHeight.toPx() } + + var uiOffset by remember { mutableFloatStateOf(0f) } + + val animatedOffset by animateFloatAsState( + targetValue = uiOffset, + animationSpec = tween(durationMillis = 150), + label = "topbar_offset" + ) + + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.y + val newOffset = (uiOffset - delta / topBarHeightPx).coerceIn(0f, 1f) + uiOffset = newOffset + return Offset.Zero + } + } + } + + if (scrollState != null) { + LaunchedEffect(scrollState) { + snapshotFlow { scrollState.value } + .collect { scrollValue -> + if (scrollValue < 50) { + uiOffset = 0f + } + } + } + } + + val totalBottomPadding = bottomNavBarHeight + bottomToolbarHeight + + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .nestedScroll(nestedScrollConnection) + ) { + // Content with padding values - content must handle scrolling + content( + PaddingValues( + top = totalTopBarHeight, + bottom = totalBottomPadding + ) + ) + + // TopBar as overlay + Column( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + .graphicsLayer { + translationY = -topBarHeightPx * animatedOffset + } + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + ) { + topBarContent() + } + + // Bottom bar + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = bottomNavBarHeight) + ) { + bottomBar() + } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/source/ServerSourceLabel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/source/ServerSourceLabel.kt new file mode 100644 index 000000000..bdbb548dc --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/source/ServerSourceLabel.kt @@ -0,0 +1,26 @@ +package dev.minios.pdaiv1.presentation.widget.source + +import androidx.compose.runtime.Composable +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +@Composable +fun ServerSource.getName(): String { + return getNameUiText().asString() +} + +fun ServerSource.getNameUiText(): UiText = when (this) { + ServerSource.AUTOMATIC1111 -> LocalizationR.string.srv_type_own + ServerSource.HORDE -> LocalizationR.string.srv_type_horde + ServerSource.LOCAL_MICROSOFT_ONNX -> LocalizationR.string.srv_type_local + ServerSource.LOCAL_GOOGLE_MEDIA_PIPE -> LocalizationR.string.srv_type_media_pipe + ServerSource.LOCAL_QUALCOMM_QNN -> LocalizationR.string.srv_type_qnn + ServerSource.HUGGING_FACE -> LocalizationR.string.srv_type_hugging_face + ServerSource.OPEN_AI -> LocalizationR.string.srv_type_open_ai + ServerSource.STABILITY_AI -> LocalizationR.string.srv_type_stability_ai + ServerSource.FAL_AI -> LocalizationR.string.srv_type_fal_ai + ServerSource.SWARM_UI -> LocalizationR.string.srv_type_swarm_ui +}.asUiText() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/toolbar/GenearionBottomToolbar.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/toolbar/GenearionBottomToolbar.kt similarity index 93% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/toolbar/GenearionBottomToolbar.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/toolbar/GenearionBottomToolbar.kt index cbc655c4b..8ac6fa159 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/toolbar/GenearionBottomToolbar.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/toolbar/GenearionBottomToolbar.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.toolbar +package dev.minios.pdaiv1.presentation.widget.toolbar import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -27,12 +27,12 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.core.GenerationMviState -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.core.localization.R as LocalizationR +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.core.GenerationMviState +import dev.minios.pdaiv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.core.localization.R as LocalizationR @Composable fun GenerationBottomToolbar( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/toolbar/ModalDialogToolbar.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/toolbar/ModalDialogToolbar.kt similarity index 94% rename from presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/toolbar/ModalDialogToolbar.kt rename to presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/toolbar/ModalDialogToolbar.kt index 5d53ad8a1..8bc552f9f 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/widget/toolbar/ModalDialogToolbar.kt +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/toolbar/ModalDialogToolbar.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.widget.toolbar +package dev.minios.pdaiv1.presentation.widget.toolbar import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -19,7 +19,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import com.shifthackz.aisdv1.core.extensions.shimmer +import dev.minios.pdaiv1.core.extensions.shimmer @Composable fun ModalDialogToolbar( diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkIntent.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkIntent.kt new file mode 100644 index 000000000..2160f7e0d --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkIntent.kt @@ -0,0 +1,7 @@ +package dev.minios.pdaiv1.presentation.widget.work + +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface BackgroundWorkIntent : MviIntent { + data object Dismiss : BackgroundWorkIntent +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkState.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkState.kt new file mode 100644 index 000000000..fc23da615 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkState.kt @@ -0,0 +1,14 @@ +package dev.minios.pdaiv1.presentation.widget.work + +import android.graphics.Bitmap +import dev.minios.pdaiv1.core.model.UiText +import com.shifthackz.android.core.mvi.MviState + +data class BackgroundWorkState( + val visible: Boolean = false, + val dismissed: Boolean = false, + val title: UiText = UiText.empty, + val subTitle: UiText = UiText.empty, + val bitmap: Bitmap? = null, + val isError: Boolean = false, +) : MviState diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkViewModel.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkViewModel.kt new file mode 100644 index 000000000..0be6d40ed --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkViewModel.kt @@ -0,0 +1,79 @@ +package dev.minios.pdaiv1.presentation.widget.work + +import dev.minios.pdaiv1.core.common.log.errorLog +import dev.minios.pdaiv1.core.common.schedulers.DispatchersProvider +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.common.schedulers.subscribeOnMainThread +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.model.UiText +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.core.viewmodel.MviRxViewModel +import dev.minios.pdaiv1.domain.entity.BackgroundWorkResult +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import com.shifthackz.android.core.mvi.EmptyEffect +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.kotlin.subscribeBy +import dev.minios.pdaiv1.core.localization.R as LocalizationR + +class BackgroundWorkViewModel( + dispatchersProvider: DispatchersProvider, + private val backgroundWorkObserver: BackgroundWorkObserver, + private val schedulersProvider: SchedulersProvider, + private val base64ToBitmapConverter: Base64ToBitmapConverter, +) : MviRxViewModel() { + + override val initialState = BackgroundWorkState() + + override val effectDispatcher = dispatchersProvider.immediate + + init { + !Flowable.combineLatest( + backgroundWorkObserver.observeStatus(), + backgroundWorkObserver.observeResult(), + ::Pair, + ) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { (work, result) -> + updateState { state -> + val resultTitle = when (result) { + is BackgroundWorkResult.Error -> LocalizationR.string.notification_fail_title.asUiText() + is BackgroundWorkResult.Success -> LocalizationR.string.notification_finish_title.asUiText() + else -> UiText.empty + } + (result as? BackgroundWorkResult.Success) + ?.ai + ?.firstOrNull() + ?.image + ?.also(::setBitmap) + + val shouldBeVisible = work.running || result !is BackgroundWorkResult.None + // Reset dismissed flag when state changes (new generation or new result) + val newDismissed = if (shouldBeVisible != state.visible) false else state.dismissed + + state.copy( + visible = shouldBeVisible && !newDismissed, + dismissed = newDismissed, + title = if (work.running) work.statusTitle.asUiText() else resultTitle, + subTitle = if (work.running) work.statusSubTitle.asUiText() else UiText.empty, + isError = !work.running && result is BackgroundWorkResult.Error, + bitmap = null, + ) + } + } + } + + override fun processIntent(intent: BackgroundWorkIntent) { + when (intent) { + BackgroundWorkIntent.Dismiss -> { + updateState { it.copy(visible = false, dismissed = true, bitmap = null) } + } + } + } + + private fun setBitmap(base64: String) = !base64ToBitmapConverter(Base64ToBitmapConverter.Input(base64)) + .map(Base64ToBitmapConverter.Output::bitmap) + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { bmp -> + updateState { it.copy(bitmap = bmp) } + } +} diff --git a/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkWidget.kt b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkWidget.kt new file mode 100644 index 000000000..c62fe6672 --- /dev/null +++ b/presentation/src/main/java/dev/minios/pdaiv1/presentation/widget/work/BackgroundWorkWidget.kt @@ -0,0 +1,207 @@ +package dev.minios.pdaiv1.presentation.widget.work + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AutoFixNormal +import androidx.compose.material.icons.filled.Error +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import dev.minios.pdaiv1.core.model.asString +import dev.minios.pdaiv1.core.model.asUiText +import com.shifthackz.android.core.mvi.MviComponent +import org.koin.androidx.compose.koinViewModel +import dev.minios.pdaiv1.core.localization.R as LocalizationR +import kotlin.math.absoluteValue +import kotlin.math.roundToInt + +@Composable +fun BackgroundWorkWidget( + modifier: Modifier = Modifier, +) { + MviComponent( + viewModel = koinViewModel(), + ) { state, processIntent -> + BackgroundWorkWidgetContent( + modifier = modifier, + state = state, + processIntent = processIntent, + ) + } +} + +@Composable +@Preview +private fun BackgroundWorkWidgetContent( + modifier: Modifier = Modifier, + state: BackgroundWorkState = BackgroundWorkState(), + processIntent: (BackgroundWorkIntent) -> Unit = {}, +) { + // Swipe-to-dismiss state (horizontal) + var offsetX by remember { mutableFloatStateOf(0f) } + val density = LocalDensity.current + val dismissThreshold = with(density) { 100.dp.toPx() } + + // Reset offset when widget becomes visible again + LaunchedEffect(state.visible) { + if (state.visible) { + offsetX = 0f + } + } + + val draggableState = rememberDraggableState { delta -> + offsetX += delta + } + + // Alpha based on drag distance + val alpha by animateFloatAsState( + targetValue = (1f - (offsetX.absoluteValue / dismissThreshold).coerceIn(0f, 1f)), + label = "swipe_alpha" + ) + + AnimatedVisibility( + modifier = modifier.fillMaxWidth(), + visible = state.visible, + enter = fadeIn(), + exit = fadeOut(), + ) { + val shape = RoundedCornerShape(16.dp) + Box( + modifier = Modifier + .fillMaxWidth() + .offset { IntOffset(offsetX.roundToInt(), 0) } + .alpha(alpha), + contentAlignment = Alignment.Center, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .background(MaterialTheme.colorScheme.surfaceTint, shape) + .clip(shape) + .draggable( + state = draggableState, + orientation = Orientation.Horizontal, + onDragStopped = { + if (offsetX.absoluteValue > dismissThreshold) { + processIntent(BackgroundWorkIntent.Dismiss) + // Keep offset so widget stays off-screen during fadeOut + } else { + offsetX = 0f + } + } + ) + .clickable( + interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource() }, + indication = null, + onClick = { /* Consume clicks to prevent pass-through */ } + ) + .padding(horizontal = 16.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(40.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(4.dp)), + contentAlignment = Alignment.Center, + ) { + if (state.visible && !state.isError && state.bitmap == null) { + CircularProgressIndicator( + modifier = Modifier + .size(40.dp) + .aspectRatio(1f), + ) + Icon( + modifier = Modifier.size(16.dp), + imageVector = Icons.Default.AutoFixNormal, + contentDescription = "Imagine", + tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f), + ) + } + if (state.visible && state.isError && state.bitmap == null) { + Icon( + modifier = Modifier.size(16.dp), + imageVector = Icons.Default.Error, + contentDescription = "Error", + tint = MaterialTheme.colorScheme.primary, + ) + } + state.bitmap?.takeIf { !state.isError }?.let { + Image( + modifier = Modifier.fillMaxSize(), + bitmap = it.asImageBitmap(), + contentDescription = null, + ) + } + } + Column { + Text( + text = state.title.asString().takeIf(String::isNotBlank) + ?: stringResource(id = LocalizationR.string.notification_pending_title), + style = MaterialTheme.typography.titleMedium, + ) + state.subTitle.asString().takeIf(String::isNotBlank)?.let { subTitle -> + Text( + text = subTitle, + style = MaterialTheme.typography.bodyMedium, + ) + } + } + } + } + } +} + +@Composable +@Preview +private fun ContentPreview() { + BackgroundWorkWidgetContent( + state = BackgroundWorkState( + visible = true, + dismissed = false, + title = "Header".asUiText(), + subTitle = "This is status message.\nThat is indeed multiline.".asUiText(), + ), + ) +} diff --git a/presentation/src/main/res/drawable/ic_pdai_logo.webp b/presentation/src/main/res/drawable/ic_pdai_logo.webp new file mode 100644 index 000000000..d19c5c9f1 Binary files /dev/null and b/presentation/src/main/res/drawable/ic_pdai_logo.webp differ diff --git a/presentation/src/main/res/drawable/ic_sdai_logo.webp b/presentation/src/main/res/drawable/ic_sdai_logo.webp deleted file mode 100644 index 468913e48..000000000 Binary files a/presentation/src/main/res/drawable/ic_sdai_logo.webp and /dev/null differ diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreGenerationMviViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreGenerationMviViewModelTest.kt deleted file mode 100644 index aa25ae1c8..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreGenerationMviViewModelTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.shifthackz.aisdv1.presentation.core - -import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider -import com.shifthackz.aisdv1.core.notification.PushNotificationManager -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator -import com.shifthackz.aisdv1.domain.entity.HordeProcessStatus -import com.shifthackz.aisdv1.domain.entity.LocalDiffusionStatus -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.interactor.wakelock.WakeLockInterActor -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.caching.SaveLastResultToCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.SaveGenerationResultUseCase -import com.shifthackz.aisdv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase -import com.shifthackz.aisdv1.domain.usecase.wakelock.AcquireWakelockUseCase -import com.shifthackz.aisdv1.domain.usecase.wakelock.ReleaseWakeLockUseCase -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import io.mockk.every -import io.mockk.mockk -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Scheduler -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.schedulers.Schedulers -import io.reactivex.rxjava3.subjects.BehaviorSubject -import org.junit.Before -import java.util.concurrent.Executor -import java.util.concurrent.Executors - -abstract class CoreGenerationMviViewModelTest> : - CoreViewModelTest() { - - protected val stubSettings = BehaviorSubject.createDefault(Settings()) - protected val stubAiForm = BehaviorSubject.create() - - protected val stubPreferenceManager = mockk() - protected val stubSaveLastResultToCacheUseCase = mockk() - protected val stubSaveGenerationResultUseCase = mockk() - protected val stubGetStableDiffusionSamplersUseCase = mockk() - protected val stubObserveHordeProcessStatusUseCase = mockk() - protected val stubObserveLocalDiffusionProcessStatusUseCase = mockk() - protected val stubInterruptGenerationUseCase = mockk() - protected val stubMainRouter = mockk() - protected val stubDrawerRouter = mockk() - protected val stubDimensionValidator = mockk() - protected val stubSdaiPushNotificationManager = mockk() - - protected val stubAcquireWakelockUseCase = mockk() - protected val stubReleaseWakelockUseCase = mockk() - protected val stubWakeLockInterActor = mockk() - protected val stubBackgroundWorkObserver = mockk() - protected val stubBackgroundTaskManager = mockk() - - private val stubHordeProcessStatus = BehaviorSubject.create() - private val stubLdStatus = BehaviorSubject.create() - - - protected val stubCustomSchedulers = object : SchedulersProvider { - override val io: Scheduler = Schedulers.io() - override val ui: Scheduler = AndroidSchedulers.mainThread() - override val computation: Scheduler = Schedulers.trampoline() - override val singleThread: Executor = Executors.newSingleThreadExecutor() - } - - @Before - override fun initialize() { - super.initialize() - - every { - stubPreferenceManager.observe() - } returns stubSettings.toFlowable(BackpressureStrategy.LATEST) - - every { - stubObserveHordeProcessStatusUseCase() - } returns stubHordeProcessStatus.toFlowable(BackpressureStrategy.LATEST) - - every { - stubObserveLocalDiffusionProcessStatusUseCase() - } returns stubLdStatus - - every { - stubGetStableDiffusionSamplersUseCase() - } returns Single.just(emptyList()) - - every { - stubAcquireWakelockUseCase.invoke(any()) - } returns Result.success(Unit) - - every { - stubAcquireWakelockUseCase.invoke() - } returns Result.success(Unit) - - every { - stubReleaseWakelockUseCase.invoke() - } returns Result.success(Unit) - - every { - stubWakeLockInterActor::acquireWakelockUseCase.get() - } returns stubAcquireWakelockUseCase - - every { - stubWakeLockInterActor::releaseWakeLockUseCase.get() - } returns stubReleaseWakelockUseCase - } -} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreViewModelInitializeStrategy.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreViewModelInitializeStrategy.kt deleted file mode 100644 index 5a247d66e..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreViewModelInitializeStrategy.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.shifthackz.aisdv1.presentation.core - -enum class CoreViewModelInitializeStrategy { - InitializeOnce, - InitializeEveryTime; -} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/AiGenerationResultMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/AiGenerationResultMocks.kt deleted file mode 100644 index 26188bfb9..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/AiGenerationResultMocks.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import java.util.Date - -val mockAiGenerationResult = AiGenerationResult( - id = 5598L, - image = "img", - inputImage = "inp", - createdAt = Date(0), - type = AiGenerationResult.Type.IMAGE_TO_IMAGE, - prompt = "prompt", - negativePrompt = "negative", - width = 512, - height = 512, - samplingSteps = 7, - cfgScale = 0.7f, - restoreFaces = true, - sampler = "sampler", - seed = "5598", - subSeed = "1504", - subSeedStrength = 5598f, - denoisingStrength = 1504f, - hidden = false, -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/HuggingFaceModelMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/HuggingFaceModelMocks.kt deleted file mode 100644 index a18196bb0..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/HuggingFaceModelMocks.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.HuggingFaceModel - -val mockHuggingFaceModels = listOf( - HuggingFaceModel.default, - HuggingFaceModel( - "80974f2d-7ee0-48e5-97bc-448de3c1d634", - "Analog Diffusion", - "wavymulder/Analog-Diffusion", - "https://huggingface.co/wavymulder/Analog-Diffusion", - ), -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/LocalAiModelMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/LocalAiModelMocks.kt deleted file mode 100644 index 62884de93..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/LocalAiModelMocks.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.LocalAiModel -import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupState - -val mockLocalAiModels = listOf( - LocalAiModel.CustomOnnx, - LocalAiModel( - id = "1", - type = LocalAiModel.Type.ONNX, - name = "Model 1", - size = "5 Gb", - sources = listOf("https://example.com/1.html"), - downloaded = false, - selected = false, - ), -) - -val mockServerSetupStateLocalModel = ServerSetupState.LocalModel( - id = "1", - name = "Model 1", - size = "5 Gb", - downloaded = false, - downloadState = DownloadState.Unknown, - selected = false, -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StabilityAiEngineMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StabilityAiEngineMocks.kt deleted file mode 100644 index e4f514ea6..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StabilityAiEngineMocks.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.StabilityAiEngine - -val mockStabilityAiEngines = listOf( - StabilityAiEngine( - id = "5598", - name = "engine_5598", - ), -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionEmbeddingMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionEmbeddingMocks.kt deleted file mode 100644 index 5b8f9b453..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionEmbeddingMocks.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.Embedding - -val mockEmbeddings = listOf( - Embedding("5598"), - Embedding("151297"), -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionLoraMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionLoraMocks.kt deleted file mode 100644 index 8cadd9724..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionLoraMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.LoRA - -val mockStableDiffusionLoras = listOf( - LoRA( - name = "name_5598", - alias = "alias_5598", - path = "/unknown", - ), - LoRA( - name = "name_151297", - alias = "alias_151297", - path = "/unknown", - ), -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionModelMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionModelMocks.kt deleted file mode 100644 index 29149f1da..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionModelMocks.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel - -val mockStableDiffusionModels = listOf( - StableDiffusionModel( - title = "title_5598", - modelName = "name_5598", - hash = "hash_5598", - sha256 = "sha_5598", - filename = "file_5598", - config = "config_5598", - ) to true, - StableDiffusionModel( - title = "title_151297", - modelName = "name_151297", - hash = "hash_151297", - sha256 = "sha_151297", - filename = "file_151297", - config = "config_151297", - ) to false, -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionSamplerMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionSamplerMocks.kt deleted file mode 100644 index 53d1f9430..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/StableDiffusionSamplerMocks.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler - -val mockStableDiffusionSamplers = listOf( - StableDiffusionSampler( - name = "sampler_1", - aliases = listOf("alias_1"), - options = mapOf("option" to "value"), - ), - StableDiffusionSampler( - name = "sampler_2", - aliases = listOf("alias_2"), - options = mapOf("option" to "value"), - ), -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/SupporterMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/SupporterMocks.kt deleted file mode 100644 index 8116e28b9..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/SupporterMocks.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.Supporter -import java.util.Date - -val mockSupporters = listOf( - Supporter( - id = 5598, - name = "NZ", - date = Date(5598L), - message = "I always wanted support you ❤", - ), -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/SwarmUiModelMocks.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/SwarmUiModelMocks.kt deleted file mode 100644 index 167c49a17..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/mocks/SwarmUiModelMocks.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.mocks - -import com.shifthackz.aisdv1.domain.entity.SwarmUiModel - -val mockSwarmUiModels = listOf( - SwarmUiModel( - name = "5598", - title = "5598", - author = "ShiftHackZ", - ) -) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryViewModelTest.kt deleted file mode 100644 index ac53410e0..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryViewModelTest.kt +++ /dev/null @@ -1,166 +0,0 @@ -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.shifthackz.aisdv1.presentation.screen.gallery.list - -import android.graphics.Bitmap -import android.net.Uri -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteAllGalleryUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemsUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.GetMediaStoreInfoUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.test.setMain -import org.junit.Assert -import org.junit.Before -import org.junit.Ignore -import org.junit.Test -import java.io.File - -@Ignore("ToDo: Investigate why sometimes tests fail on remote worker due to race-conditions.") -class GalleryViewModelTest : CoreViewModelTest() { - - private val stubMediaStoreInfo = MediaStoreInfo(5598) - private val stubFile = mockk() - private val stubBitmap = mockk() - private val stubUri = mockk() - private val stubGetMediaStoreInfoUseCase = mockk() - private val stubGetGenerationResultPagedUseCase = mockk() - private val stubBase64ToBitmapConverter = mockk() - private val stubGalleryExporter = mockk() - private val stubMainRouter = mockk() - private val stubDrawerRouter = mockk() - private val stubDeleteAllGalleryUseCase = mockk() - private val stubDeleteGalleryItemsUseCase = mockk() - private val stubBackgroundWorkObserver = mockk() - private val stubPreferenceManager = mockk() - - override fun initializeViewModel() = GalleryViewModel( - dispatchersProvider = stubDispatchersProvider, - getMediaStoreInfoUseCase = stubGetMediaStoreInfoUseCase, - backgroundWorkObserver = stubBackgroundWorkObserver, - preferenceManager = stubPreferenceManager, - getGenerationResultPagedUseCase = stubGetGenerationResultPagedUseCase, - base64ToBitmapConverter = stubBase64ToBitmapConverter, - galleryExporter = stubGalleryExporter, - schedulersProvider = stubSchedulersProvider, - mainRouter = stubMainRouter, - drawerRouter = stubDrawerRouter, - deleteAllGalleryUseCase = stubDeleteAllGalleryUseCase, - deleteGalleryItemsUseCase = stubDeleteGalleryItemsUseCase, - ) - - @Before - override fun initialize() { - super.initialize() - - every { - stubPreferenceManager.observe() - } returns Flowable.just(Settings()) - - every { - stubBackgroundWorkObserver.observeResult() - } returns Flowable.empty() - - every { - stubGetMediaStoreInfoUseCase() - } returns Single.just(stubMediaStoreInfo) - } - - @Test - fun `initialized, expected mediaStoreInfo field in UI state equals stubMediaStoreInfo`() { - runTest { - val expected = stubMediaStoreInfo - val actual = viewModel.state.value.mediaStoreInfo - Assert.assertEquals(expected, actual) - } - } - - @Test - fun `given received DismissDialog intent, expected screenModal field in UI state is None`() { - viewModel.processIntent(GalleryIntent.DismissDialog) - runTest { - val expected = Modal.None - val actual = viewModel.state.value.screenModal - Assert.assertEquals(expected, actual) - } - } - - @Test - fun `given received Export Request intent, expected screenModal field in UI state is ConfirmExport`() { - viewModel.processIntent(GalleryIntent.Export.All.Request) - runTest { - val expected = Modal.ConfirmExport(true) - val actual = viewModel.state.value.screenModal - Assert.assertEquals(expected, actual) - } - } - - @Test - fun `given received Export Confirm intent, expected screenModal field in UI state is None, Share effect delivered to effect collector`() { - every { - stubGalleryExporter() - } returns Single.just(stubFile) - - Dispatchers.setMain(UnconfinedTestDispatcher()) - - viewModel.processIntent(GalleryIntent.Export.All.Confirm) - - runTest { - val expectedUiState = Modal.None - val actualUiState = viewModel.state.value.screenModal - Assert.assertEquals(expectedUiState, actualUiState) - - val expectedEffect = GalleryEffect.Share(stubFile) - val actualEffect = viewModel.effect.firstOrNull() - Assert.assertEquals(expectedEffect, actualEffect) - } - verify { - stubGalleryExporter() - } - } - - @Test - fun `given received OpenItem intent, expected router navigateToGalleryDetails() method called`() { - every { - stubMainRouter.navigateToGalleryDetails(any()) - } returns Unit - - val item = GalleryGridItemUi(5598L, stubBitmap, false) - viewModel.processIntent(GalleryIntent.OpenItem(item)) - - verify { - stubMainRouter.navigateToGalleryDetails(5598L) - } - } - - @Test - fun `given received OpenMediaStoreFolder intent, expected OpenUri effect delivered to effect collector`() { - Dispatchers.setMain(UnconfinedTestDispatcher()) - viewModel.processIntent(GalleryIntent.OpenMediaStoreFolder(stubUri)) - runTest { - val expected = GalleryEffect.OpenUri(stubUri) - val actual = viewModel.effect.firstOrNull() - Assert.assertEquals(expected, actual) - } - } -} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/stub/DispatchersProviderStub.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/stub/DispatchersProviderStub.kt deleted file mode 100644 index f5fce850e..000000000 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/stub/DispatchersProviderStub.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.presentation.stub - -import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers - -val stubDispatchersProvider = object : DispatchersProvider { - override val io: CoroutineDispatcher = Dispatchers.Default - override val ui: CoroutineDispatcher = Dispatchers.Default - override val immediate: CoroutineDispatcher = Dispatchers.Default -} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionViewModelTest.kt similarity index 90% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionViewModelTest.kt index a943e9337..1d3ea3090 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/activity/AiStableDiffusionViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/activity/AiStableDiffusionViewModelTest.kt @@ -1,20 +1,20 @@ @file:OptIn(ExperimentalCoroutinesApi::class) -package com.shifthackz.aisdv1.presentation.activity +package dev.minios.pdaiv1.presentation.activity import androidx.navigation.NavOptionsBuilder import app.cash.turbine.test -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.presentation.core.CoreViewModelInitializeStrategy -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.navigation.router.home.HomeRouter -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.presentation.core.CoreViewModelInitializeStrategy +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.home.HomeRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreComposeTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreComposeTest.kt similarity index 97% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreComposeTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreComposeTest.kt index b7a857deb..8790f7ec4 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreComposeTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreComposeTest.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.presentation.core +package dev.minios.pdaiv1.presentation.core import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.assertIsDisplayed diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreGenerationMviViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreGenerationMviViewModelTest.kt new file mode 100644 index 000000000..6ca9cc999 --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreGenerationMviViewModelTest.kt @@ -0,0 +1,112 @@ +package dev.minios.pdaiv1.presentation.core + +import dev.minios.pdaiv1.core.common.schedulers.SchedulersProvider +import dev.minios.pdaiv1.core.notification.PushNotificationManager +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator +import dev.minios.pdaiv1.domain.entity.HordeProcessStatus +import dev.minios.pdaiv1.domain.entity.LocalDiffusionStatus +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActor +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.caching.SaveLastResultToCacheUseCase +import dev.minios.pdaiv1.domain.usecase.generation.InterruptGenerationUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase +import dev.minios.pdaiv1.domain.usecase.generation.SaveGenerationResultUseCase +import dev.minios.pdaiv1.domain.usecase.sdsampler.GetStableDiffusionSamplersUseCase +import dev.minios.pdaiv1.domain.usecase.forgemodule.GetForgeModulesUseCase +import dev.minios.pdaiv1.domain.usecase.wakelock.AcquireWakelockUseCase +import dev.minios.pdaiv1.domain.usecase.wakelock.ReleaseWakeLockUseCase +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import io.mockk.every +import io.mockk.mockk +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.BackpressureStrategy +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import io.reactivex.rxjava3.subjects.BehaviorSubject +import org.junit.Before +import java.util.concurrent.Executor +import java.util.concurrent.Executors + +abstract class CoreGenerationMviViewModelTest> : + CoreViewModelTest() { + + protected val stubSettings = BehaviorSubject.createDefault(Settings()) + protected val stubAiForm = BehaviorSubject.create() + + protected val stubPreferenceManager = mockk() + protected val stubSaveLastResultToCacheUseCase = mockk() + protected val stubSaveGenerationResultUseCase = mockk() + protected val stubGetStableDiffusionSamplersUseCase = mockk() + protected val stubObserveHordeProcessStatusUseCase = mockk() + protected val stubObserveLocalDiffusionProcessStatusUseCase = mockk() + protected val stubInterruptGenerationUseCase = mockk() + protected val stubMainRouter = mockk() + protected val stubDrawerRouter = mockk() + protected val stubDimensionValidator = mockk() + protected val stubPdaiPushNotificationManager = mockk() + + protected val stubAcquireWakelockUseCase = mockk() + protected val stubReleaseWakelockUseCase = mockk() + protected val stubWakeLockInterActor = mockk() + protected val stubBackgroundWorkObserver = mockk() + protected val stubBackgroundTaskManager = mockk() + protected val stubGetForgeModulesUseCase = mockk() + + private val stubHordeProcessStatus = BehaviorSubject.create() + private val stubLdStatus = BehaviorSubject.create() + + + protected val stubCustomSchedulers = object : SchedulersProvider { + override val io: Scheduler = Schedulers.io() + override val ui: Scheduler = AndroidSchedulers.mainThread() + override val computation: Scheduler = Schedulers.trampoline() + override val singleThread: Executor = Executors.newSingleThreadExecutor() + } + + @Before + override fun initialize() { + super.initialize() + + every { + stubPreferenceManager.observe() + } returns stubSettings.toFlowable(BackpressureStrategy.LATEST) + + every { + stubObserveHordeProcessStatusUseCase() + } returns stubHordeProcessStatus.toFlowable(BackpressureStrategy.LATEST) + + every { + stubObserveLocalDiffusionProcessStatusUseCase() + } returns stubLdStatus + + every { + stubGetStableDiffusionSamplersUseCase() + } returns Single.just(emptyList()) + + every { + stubAcquireWakelockUseCase.invoke(any()) + } returns Result.success(Unit) + + every { + stubAcquireWakelockUseCase.invoke() + } returns Result.success(Unit) + + every { + stubReleaseWakelockUseCase.invoke() + } returns Result.success(Unit) + + every { + stubWakeLockInterActor::acquireWakelockUseCase.get() + } returns stubAcquireWakelockUseCase + + every { + stubWakeLockInterActor::releaseWakeLockUseCase.get() + } returns stubReleaseWakelockUseCase + } +} diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreViewModelInitializeStrategy.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreViewModelInitializeStrategy.kt new file mode 100644 index 000000000..2b8fb723e --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreViewModelInitializeStrategy.kt @@ -0,0 +1,6 @@ +package dev.minios.pdaiv1.presentation.core + +enum class CoreViewModelInitializeStrategy { + InitializeOnce, + InitializeEveryTime; +} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreViewModelTest.kt similarity index 96% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreViewModelTest.kt index 9a0c4a9a0..e86146925 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/core/CoreViewModelTest.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalCoroutinesApi::class) -package com.shifthackz.aisdv1.presentation.core +package dev.minios.pdaiv1.presentation.core import androidx.lifecycle.ViewModel import kotlinx.coroutines.CoroutineDispatcher diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/AiGenerationResultMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/AiGenerationResultMocks.kt new file mode 100644 index 000000000..57fdd201f --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/AiGenerationResultMocks.kt @@ -0,0 +1,26 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import java.util.Date + +val mockAiGenerationResult = AiGenerationResult( + id = 5598L, + image = "img", + inputImage = "inp", + createdAt = Date(0), + type = AiGenerationResult.Type.IMAGE_TO_IMAGE, + prompt = "prompt", + negativePrompt = "negative", + width = 512, + height = 512, + samplingSteps = 7, + cfgScale = 0.7f, + restoreFaces = true, + sampler = "sampler", + seed = "5598", + subSeed = "1504", + subSeedStrength = 5598f, + denoisingStrength = 1504f, + hidden = false, + modelName = "MockModel", +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/HuggingFaceModelMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/HuggingFaceModelMocks.kt new file mode 100644 index 000000000..4a2b2e58f --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/HuggingFaceModelMocks.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.HuggingFaceModel + +val mockHuggingFaceModels = listOf( + HuggingFaceModel.default, + HuggingFaceModel( + "80974f2d-7ee0-48e5-97bc-448de3c1d634", + "Analog Diffusion", + "wavymulder/Analog-Diffusion", + "https://huggingface.co/wavymulder/Analog-Diffusion", + ), +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/LocalAiModelMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/LocalAiModelMocks.kt new file mode 100644 index 000000000..0e789c4ee --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/LocalAiModelMocks.kt @@ -0,0 +1,27 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.LocalAiModel +import dev.minios.pdaiv1.presentation.screen.setup.ServerSetupState + +val mockLocalAiModels = listOf( + LocalAiModel.CustomOnnx, + LocalAiModel( + id = "1", + type = LocalAiModel.Type.ONNX, + name = "Model 1", + size = "5 Gb", + sources = listOf("https://example.com/1.html"), + downloaded = false, + selected = false, + ), +) + +val mockServerSetupStateLocalModel = ServerSetupState.LocalModel( + id = "1", + name = "Model 1", + size = "5 Gb", + downloaded = false, + downloadState = DownloadState.Unknown, + selected = false, +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StabilityAiEngineMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StabilityAiEngineMocks.kt new file mode 100644 index 000000000..2f3c0f705 --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StabilityAiEngineMocks.kt @@ -0,0 +1,10 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.StabilityAiEngine + +val mockStabilityAiEngines = listOf( + StabilityAiEngine( + id = "5598", + name = "engine_5598", + ), +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionEmbeddingMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionEmbeddingMocks.kt new file mode 100644 index 000000000..515dc4036 --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionEmbeddingMocks.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.Embedding + +val mockEmbeddings = listOf( + Embedding("5598"), + Embedding("151297"), +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionLoraMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionLoraMocks.kt new file mode 100644 index 000000000..5d8ecf955 --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionLoraMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.LoRA + +val mockStableDiffusionLoras = listOf( + LoRA( + name = "name_5598", + alias = "alias_5598", + path = "/unknown", + ), + LoRA( + name = "name_151297", + alias = "alias_151297", + path = "/unknown", + ), +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionModelMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionModelMocks.kt new file mode 100644 index 000000000..ff3044280 --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionModelMocks.kt @@ -0,0 +1,22 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionModel + +val mockStableDiffusionModels = listOf( + StableDiffusionModel( + title = "title_5598", + modelName = "name_5598", + hash = "hash_5598", + sha256 = "sha_5598", + filename = "file_5598", + config = "config_5598", + ) to true, + StableDiffusionModel( + title = "title_151297", + modelName = "name_151297", + hash = "hash_151297", + sha256 = "sha_151297", + filename = "file_151297", + config = "config_151297", + ) to false, +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionSamplerMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionSamplerMocks.kt new file mode 100644 index 000000000..1a30b90cc --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/StableDiffusionSamplerMocks.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler + +val mockStableDiffusionSamplers = listOf( + StableDiffusionSampler( + name = "sampler_1", + aliases = listOf("alias_1"), + options = mapOf("option" to "value"), + ), + StableDiffusionSampler( + name = "sampler_2", + aliases = listOf("alias_2"), + options = mapOf("option" to "value"), + ), +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/SupporterMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/SupporterMocks.kt new file mode 100644 index 000000000..9a4306b9b --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/SupporterMocks.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.Supporter +import java.util.Date + +val mockSupporters = listOf( + Supporter( + id = 5598, + name = "NZ", + date = Date(5598L), + message = "I always wanted support you ❤", + ), +) diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/SwarmUiModelMocks.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/SwarmUiModelMocks.kt new file mode 100644 index 000000000..1b1393466 --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/mocks/SwarmUiModelMocks.kt @@ -0,0 +1,11 @@ +package dev.minios.pdaiv1.presentation.mocks + +import dev.minios.pdaiv1.domain.entity.SwarmUiModel + +val mockSwarmUiModels = listOf( + SwarmUiModel( + name = "5598", + title = "5598", + author = "ShiftHackZ", + ) +) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingViewModelTest.kt similarity index 88% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingViewModelTest.kt index 100fa9f9f..c574ff7d0 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/embedding/EmbeddingViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/embedding/EmbeddingViewModelTest.kt @@ -1,14 +1,14 @@ -package com.shifthackz.aisdv1.presentation.modal.embedding - -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.mocks.mockEmbeddings -import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasEffect -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +package dev.minios.pdaiv1.presentation.modal.embedding + +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.sdembedding.FetchAndGetEmbeddingsUseCase +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.mocks.mockEmbeddings +import dev.minios.pdaiv1.presentation.modal.extras.ExtrasEffect +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasViewModelTest.kt similarity index 87% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasViewModelTest.kt index 2e503c53b..361651ee0 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/extras/ExtrasViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/extras/ExtrasViewModelTest.kt @@ -1,16 +1,16 @@ -package com.shifthackz.aisdv1.presentation.modal.extras - -import com.shifthackz.aisdv1.core.common.time.TimeProvider -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCase -import com.shifthackz.aisdv1.domain.usecase.sdlora.FetchAndGetLorasUseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.mocks.mockStableDiffusionLoras -import com.shifthackz.aisdv1.presentation.model.ErrorState -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +package dev.minios.pdaiv1.presentation.modal.extras + +import dev.minios.pdaiv1.core.common.time.TimeProvider +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.sdhypernet.FetchAndGetHyperNetworksUseCase +import dev.minios.pdaiv1.domain.usecase.sdlora.FetchAndGetLorasUseCase +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.mocks.mockStableDiffusionLoras +import dev.minios.pdaiv1.presentation.model.ErrorState +import dev.minios.pdaiv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.reactivex.rxjava3.core.Single diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryViewModelTest.kt new file mode 100644 index 000000000..4d91cb654 --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/history/InputHistoryViewModelTest.kt @@ -0,0 +1,40 @@ +package dev.minios.pdaiv1.presentation.modal.history + +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCase +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Test +import com.shifthackz.android.core.mvi.EmptyState + +class InputHistoryViewModelTest : CoreViewModelTest() { + + private val stubGetGenerationResultPagedUseCase = mockk() + private val stubBase64ToBitmapConverter = mockk() + + override fun initializeViewModel() = InputHistoryViewModel( + dispatchersProvider = stubDispatchersProvider, + getGenerationResultPagedUseCase = stubGetGenerationResultPagedUseCase, + base64ToBitmapConverter = stubBase64ToBitmapConverter, + schedulersProvider = stubSchedulersProvider, + ) + + @Test + fun `initialized, expected initialState is EmptyState`() { + runTest { + val state = viewModel.state.value + Assert.assertEquals(EmptyState, state) + } + } + + @Test + fun `initialized, expected pagingFlow is not null`() { + runTest { + Assert.assertNotNull(viewModel.pagingFlow) + } + } +} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagViewModelTest.kt similarity index 93% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagViewModelTest.kt index 4035f93f7..797922aef 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/modal/tag/EditTagViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/modal/tag/EditTagViewModelTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.modal.tag +package dev.minios.pdaiv1.presentation.modal.tag -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.model.ExtraType -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.model.ExtraType +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.test.runTest import org.junit.Assert diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouterImplTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouterImplTest.kt similarity index 88% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouterImplTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouterImplTest.kt index dbc4295d6..402bc92c1 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/drawer/DrawerRouterImplTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/navigation/router/drawer/DrawerRouterImplTest.kt @@ -1,6 +1,6 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.drawer +package dev.minios.pdaiv1.presentation.navigation.router.drawer -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect import org.junit.Test class DrawerRouterImplTest { diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterImplTest.kt similarity index 93% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterImplTest.kt index af3c57efe..0e6071ac7 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/navigation/router/main/MainRouterImplTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.navigation.router.main +package dev.minios.pdaiv1.presentation.navigation.router.main -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect -import com.shifthackz.aisdv1.presentation.navigation.NavigationRoute +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.navigation.NavigationEffect +import dev.minios.pdaiv1.presentation.navigation.NavigationRoute import org.junit.Test class MainRouterImplTest { diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuViewModelTest.kt similarity index 75% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuViewModelTest.kt index 537596865..8e28686d8 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/debug/DebugMenuViewModelTest.kt @@ -1,14 +1,14 @@ -package com.shifthackz.aisdv1.presentation.screen.debug +package dev.minios.pdaiv1.presentation.screen.debug -import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +import dev.minios.pdaiv1.core.common.file.FileProviderDescriptor +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.feature.work.BackgroundTaskManager +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.debug.DebugInsertBadBase64UseCase +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/donate/DonateViewModelTest.kt similarity index 81% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/donate/DonateViewModelTest.kt index f65d03966..a034b6dfa 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/donate/DonateViewModelTest.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.presentation.screen.donate +package dev.minios.pdaiv1.presentation.screen.donate -import com.shifthackz.aisdv1.domain.usecase.donate.FetchAndGetSupportersUseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.mocks.mockSupporters -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +import dev.minios.pdaiv1.domain.usecase.donate.FetchAndGetSupportersUseCase +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.mocks.mockSupporters +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerViewModelTest.kt similarity index 80% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerViewModelTest.kt index 9f5983f7b..915d1713d 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/drawer/DrawerViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/drawer/DrawerViewModelTest.kt @@ -1,8 +1,8 @@ -package com.shifthackz.aisdv1.presentation.screen.drawer +package dev.minios.pdaiv1.presentation.screen.drawer -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailViewModelTest.kt similarity index 75% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailViewModelTest.kt index 6e24e48cf..6a0a1df73 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/gallery/detail/GalleryDetailViewModelTest.kt @@ -1,29 +1,36 @@ @file:OptIn(ExperimentalCoroutinesApi::class) -package com.shifthackz.aisdv1.presentation.screen.gallery.detail +package dev.minios.pdaiv1.presentation.screen.gallery.detail import android.graphics.Bitmap import app.cash.turbine.test -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.model.asUiText -import com.shifthackz.aisdv1.domain.entity.AiGenerationResult -import com.shifthackz.aisdv1.domain.usecase.caching.GetLastResultFromCacheUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemUseCase -import com.shifthackz.aisdv1.domain.usecase.gallery.ToggleImageVisibilityUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultUseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.extensions.mapToUi -import com.shifthackz.aisdv1.presentation.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.model.asUiText +import dev.minios.pdaiv1.domain.entity.AiGenerationResult +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.usecase.caching.GetLastResultFromCacheUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryPagedIdsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleImageVisibilityUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.ToggleLikeUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultUseCase +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.core.GalleryItemStateEvent +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.extensions.mapToUi +import dev.minios.pdaiv1.presentation.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.firstOrNull @@ -44,10 +51,16 @@ class GalleryDetailViewModelTest : CoreViewModelTest() { private val stubGetLastResultFromCacheUseCase = mockk() private val stubDeleteGalleryItemUseCase = mockk() private val stubToggleImageVisibilityUseCase = mockk() + private val stubToggleLikeUseCase = mockk() private val stubGalleryDetailBitmapExporter = mockk() private val stubBase64ToBitmapConverter = mockk() private val stubGenerationFormUpdateEvent = mockk() + private val stubGalleryItemStateEvent = mockk() private val stubMainRouter = mockk() + private val stubPreferenceManager = mockk() + private val stubGetGalleryPagedIdsUseCase = mockk() + private val stubMediaStoreGateway = mockk() + private val stubBackgroundWorkObserver = mockk() override fun initializeViewModel() = GalleryDetailViewModel( itemId = 5598L, @@ -57,11 +70,17 @@ class GalleryDetailViewModelTest : CoreViewModelTest() { getLastResultFromCacheUseCase = stubGetLastResultFromCacheUseCase, deleteGalleryItemUseCase = stubDeleteGalleryItemUseCase, toggleImageVisibilityUseCase = stubToggleImageVisibilityUseCase, + toggleLikeUseCase = stubToggleLikeUseCase, galleryDetailBitmapExporter = stubGalleryDetailBitmapExporter, base64ToBitmapConverter = stubBase64ToBitmapConverter, schedulersProvider = stubSchedulersProvider, generationFormUpdateEvent = stubGenerationFormUpdateEvent, + galleryItemStateEvent = stubGalleryItemStateEvent, mainRouter = stubMainRouter, + preferenceManager = stubPreferenceManager, + getGalleryPagedIdsUseCase = stubGetGalleryPagedIdsUseCase, + mediaStoreGateway = stubMediaStoreGateway, + backgroundWorkObserver = stubBackgroundWorkObserver, ) @Before @@ -79,6 +98,10 @@ class GalleryDetailViewModelTest : CoreViewModelTest() { every { stubBase64ToBitmapConverter(any()) } returns Single.just(Base64ToBitmapConverter.Output(stubBitmap)) + + every { + stubBackgroundWorkObserver.observeGalleryChanged() + } returns Flowable.empty() } @Test @@ -270,15 +293,44 @@ class GalleryDetailViewModelTest : CoreViewModelTest() { } @Test - fun `given received ToggleVisibility intent, expected`() { + fun `given received ToggleVisibility intent, expected hidden state updated and event emitted`() { every { stubToggleImageVisibilityUseCase(any()) } returns Single.just(true) + every { + stubGalleryItemStateEvent.emitHiddenChange(any(), any()) + } returns Unit + viewModel.processIntent(GalleryDetailIntent.ToggleVisibility) val expected = true val actual = (viewModel.state.value as? GalleryDetailState.Content)?.hidden Assert.assertEquals(expected, actual) + + verify { + stubGalleryItemStateEvent.emitHiddenChange(5598L, true) + } + } + + @Test + fun `given received ToggleLike intent, expected liked state updated and event emitted`() { + every { + stubToggleLikeUseCase(any()) + } returns Single.just(true) + + every { + stubGalleryItemStateEvent.emitLikedChange(any(), any()) + } returns Unit + + viewModel.processIntent(GalleryDetailIntent.ToggleLike) + + val expected = true + val actual = (viewModel.state.value as? GalleryDetailState.Content)?.liked + Assert.assertEquals(expected, actual) + + verify { + stubGalleryItemStateEvent.emitLikedChange(5598L, true) + } } } diff --git a/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryViewModelTest.kt new file mode 100644 index 000000000..228237cfe --- /dev/null +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/gallery/list/GalleryViewModelTest.kt @@ -0,0 +1,293 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package dev.minios.pdaiv1.presentation.screen.gallery.list + +import android.graphics.Bitmap +import android.net.Uri +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.ThumbnailGenerator +import dev.minios.pdaiv1.domain.entity.MediaStoreInfo +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.feature.work.BackgroundWorkObserver +import dev.minios.pdaiv1.domain.feature.MediaFileManager +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllGalleryUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteAllUnlikedUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.DeleteGalleryItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetAllGalleryUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryItemsRawUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetGalleryPagedIdsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetMediaStoreInfoUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.GetThumbnailInfoUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.LikeItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.UnlikeItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.HideItemsUseCase +import dev.minios.pdaiv1.domain.usecase.gallery.UnhideItemsUseCase +import dev.minios.pdaiv1.domain.usecase.generation.GetGenerationResultPagedUseCase +import dev.minios.pdaiv1.domain.gateway.MediaStoreGateway +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.core.GalleryItemStateEvent +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.drawer.DrawerRouter +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.Assert +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import java.io.File + +@Ignore("ToDo: Investigate why sometimes tests fail on remote worker due to race-conditions.") +class GalleryViewModelTest : CoreViewModelTest() { + + private val stubMediaStoreInfo = MediaStoreInfo(5598) + private val stubFile = mockk() + private val stubBitmap = mockk() + private val stubUri = mockk() + private val stubGetMediaStoreInfoUseCase = mockk() + private val stubGetGenerationResultPagedUseCase = mockk() + private val stubGetGalleryPagedIdsUseCase = mockk() + private val stubGetGalleryItemsUseCase = mockk() + private val stubGetGalleryItemsRawUseCase = mockk() + private val stubGetThumbnailInfoUseCase = mockk() + private val stubBase64ToBitmapConverter = mockk() + private val stubThumbnailGenerator = mockk() + private val stubGalleryExporter = mockk() + private val stubMainRouter = mockk() + private val stubDrawerRouter = mockk() + private val stubDeleteAllGalleryUseCase = mockk() + private val stubDeleteAllUnlikedUseCase = mockk() + private val stubDeleteGalleryItemsUseCase = mockk() + private val stubBackgroundWorkObserver = mockk() + private val stubPreferenceManager = mockk() + private val stubMediaStoreGateway = mockk() + private val stubMediaFileManager = mockk() + private val stubGetAllGalleryUseCase = mockk() + private val stubGalleryItemStateEvent = mockk() + private val stubLikeItemsUseCase = mockk() + private val stubUnlikeItemsUseCase = mockk() + private val stubHideItemsUseCase = mockk() + private val stubUnhideItemsUseCase = mockk() + + override fun initializeViewModel() = GalleryViewModel( + dispatchersProvider = stubDispatchersProvider, + getMediaStoreInfoUseCase = stubGetMediaStoreInfoUseCase, + backgroundWorkObserver = stubBackgroundWorkObserver, + preferenceManager = stubPreferenceManager, + deleteAllGalleryUseCase = stubDeleteAllGalleryUseCase, + deleteAllUnlikedUseCase = stubDeleteAllUnlikedUseCase, + deleteGalleryItemsUseCase = stubDeleteGalleryItemsUseCase, + getGenerationResultPagedUseCase = stubGetGenerationResultPagedUseCase, + getGalleryPagedIdsUseCase = stubGetGalleryPagedIdsUseCase, + getGalleryItemsUseCase = stubGetGalleryItemsUseCase, + getGalleryItemsRawUseCase = stubGetGalleryItemsRawUseCase, + getThumbnailInfoUseCase = stubGetThumbnailInfoUseCase, + base64ToBitmapConverter = stubBase64ToBitmapConverter, + thumbnailGenerator = stubThumbnailGenerator, + galleryExporter = stubGalleryExporter, + schedulersProvider = stubSchedulersProvider, + mainRouter = stubMainRouter, + drawerRouter = stubDrawerRouter, + mediaStoreGateway = stubMediaStoreGateway, + mediaFileManager = stubMediaFileManager, + getAllGalleryUseCase = stubGetAllGalleryUseCase, + galleryItemStateEvent = stubGalleryItemStateEvent, + likeItemsUseCase = stubLikeItemsUseCase, + unlikeItemsUseCase = stubUnlikeItemsUseCase, + hideItemsUseCase = stubHideItemsUseCase, + unhideItemsUseCase = stubUnhideItemsUseCase, + ) + + @Before + override fun initialize() { + super.initialize() + + every { + stubPreferenceManager.observe() + } returns Flowable.just(Settings()) + + every { + stubBackgroundWorkObserver.observeResult() + } returns Flowable.empty() + + every { + stubBackgroundWorkObserver.observeNewImage() + } returns Flowable.empty() + + every { + stubBackgroundWorkObserver.observeGalleryChanged() + } returns Flowable.empty() + + every { + stubGetMediaStoreInfoUseCase() + } returns Single.just(stubMediaStoreInfo) + + every { + stubGetGalleryPagedIdsUseCase.withBlurHash() + } returns Single.just(emptyList()) + + every { + stubGalleryItemStateEvent.observeHiddenChanges() + } returns Flowable.empty() + + every { + stubGalleryItemStateEvent.observeLikedChanges() + } returns Flowable.empty() + } + + @Test + fun `initialized, expected mediaStoreInfo field in UI state equals stubMediaStoreInfo`() { + runTest { + val expected = stubMediaStoreInfo + val actual = viewModel.state.value.mediaStoreInfo + Assert.assertEquals(expected, actual) + } + } + + @Test + fun `given received DismissDialog intent, expected screenModal field in UI state is None`() { + viewModel.processIntent(GalleryIntent.DismissDialog) + runTest { + val expected = Modal.None + val actual = viewModel.state.value.screenModal + Assert.assertEquals(expected, actual) + } + } + + @Test + fun `given received Export Request intent, expected screenModal field in UI state is ConfirmExport`() { + viewModel.processIntent(GalleryIntent.Export.All.Request) + runTest { + val expected = Modal.ConfirmExport(true) + val actual = viewModel.state.value.screenModal + Assert.assertEquals(expected, actual) + } + } + + @Test + fun `given received Export Confirm intent, expected screenModal field in UI state is None, Share effect delivered to effect collector`() { + every { + stubGalleryExporter() + } returns Single.just(stubFile) + + Dispatchers.setMain(UnconfinedTestDispatcher()) + + viewModel.processIntent(GalleryIntent.Export.All.Confirm) + + runTest { + val expectedUiState = Modal.None + val actualUiState = viewModel.state.value.screenModal + Assert.assertEquals(expectedUiState, actualUiState) + + val expectedEffect = GalleryEffect.Share(stubFile) + val actualEffect = viewModel.effect.firstOrNull() + Assert.assertEquals(expectedEffect, actualEffect) + } + verify { + stubGalleryExporter() + } + } + + @Test + fun `given received OpenItem intent, expected selectedItemId in state equals item id`() { + viewModel.processIntent(GalleryIntent.OpenItem(5598L, 0)) + + runTest { + val expected = 5598L + val actual = viewModel.state.value.selectedItemId + Assert.assertEquals(expected, actual) + } + } + + @Test + fun `given received OpenMediaStoreFolder intent, expected OpenUri effect delivered to effect collector`() { + Dispatchers.setMain(UnconfinedTestDispatcher()) + viewModel.processIntent(GalleryIntent.OpenMediaStoreFolder(stubUri)) + runTest { + val expected = GalleryEffect.OpenUri(stubUri) + val actual = viewModel.effect.firstOrNull() + Assert.assertEquals(expected, actual) + } + } + + @Test + fun `given received Delete AllUnliked Request intent, expected screenModal is DeleteUnlikedConfirm`() { + viewModel.processIntent(GalleryIntent.Delete.AllUnliked.Request) + runTest { + val expected = Modal.DeleteUnlikedConfirm + val actual = viewModel.state.value.screenModal + Assert.assertEquals(expected, actual) + } + } + + @Test + fun `given received Delete AllUnliked Confirm intent, expected deleteAllUnlikedUseCase is called`() { + every { + stubDeleteAllUnlikedUseCase() + } returns Completable.complete() + + viewModel.processIntent(GalleryIntent.Delete.AllUnliked.Confirm) + + verify { + stubDeleteAllUnlikedUseCase() + } + } + + @Test + fun `given received LikeSelection intent with selection, expected likeItemsUseCase is called`() { + every { + stubLikeItemsUseCase(any()) + } returns Completable.complete() + + every { + stubGalleryItemStateEvent.emitLikedChange(any(), any()) + } returns Unit + + // Set up selection + viewModel.processIntent(GalleryIntent.ChangeSelectionMode(true)) + viewModel.processIntent(GalleryIntent.ToggleItemSelection(1L)) + viewModel.processIntent(GalleryIntent.ToggleItemSelection(2L)) + + viewModel.processIntent(GalleryIntent.LikeSelection) + + verify { + stubLikeItemsUseCase(listOf(1L, 2L)) + } + } + + @Test + fun `given received HideSelection intent with selection, expected hideItemsUseCase is called`() { + every { + stubHideItemsUseCase(any()) + } returns Completable.complete() + + every { + stubGalleryItemStateEvent.emitHiddenChange(any(), any()) + } returns Unit + + // Set up selection + viewModel.processIntent(GalleryIntent.ChangeSelectionMode(true)) + viewModel.processIntent(GalleryIntent.ToggleItemSelection(1L)) + viewModel.processIntent(GalleryIntent.ToggleItemSelection(2L)) + + viewModel.processIntent(GalleryIntent.HideSelection) + + verify { + stubHideItemsUseCase(listOf(1L, 2L)) + } + } +} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageViewModelTest.kt similarity index 91% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageViewModelTest.kt index d06dad16e..06b050b0b 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/img2img/ImageToImageViewModelTest.kt @@ -1,30 +1,30 @@ -package com.shifthackz.aisdv1.presentation.screen.img2img +package dev.minios.pdaiv1.presentation.screen.img2img import android.graphics.Bitmap -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter -import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter -import com.shifthackz.aisdv1.core.validation.ValidationResult -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator.Error -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.OpenAiQuality -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.OpenAiStyle -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import com.shifthackz.aisdv1.domain.usecase.generation.GetRandomImageUseCase -import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase -import com.shifthackz.aisdv1.presentation.core.CoreGenerationMviViewModelTest -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.presentation.model.InPaintModel -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintStateProducer -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.imageprocessing.Base64ToBitmapConverter +import dev.minios.pdaiv1.core.imageprocessing.BitmapToBase64Converter +import dev.minios.pdaiv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator.Error +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.OpenAiQuality +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.OpenAiStyle +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.domain.usecase.generation.GetRandomImageUseCase +import dev.minios.pdaiv1.domain.usecase.generation.ImageToImageUseCase +import dev.minios.pdaiv1.presentation.core.CoreGenerationMviViewModelTest +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.presentation.model.InPaintModel +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.screen.inpaint.InPaintStateProducer +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll @@ -70,13 +70,14 @@ class ImageToImageViewModelTest : CoreGenerationMviViewModelTest m.id == LocalAiModel.CustomOnnx.id }!!.copy( + scannedOnnxCustomModels = listOf( + ServerSetupState.LocalModel( + id = "custom_model", + name = "Custom Model", + size = "1GB", + downloaded = true, selected = true, - downloaded = true - ), + ) ), ) } @@ -156,7 +159,7 @@ class ServerSetupScreenTest : CoreComposeTest { setupButton .assertIsDisplayed() .assertIsEnabled() - .assertTextEquals("Setup") + .assertTextEquals("Start") switch .assertIsDisplayed() .assertIsOn() @@ -166,18 +169,14 @@ class ServerSetupScreenTest : CoreComposeTest { stubUiState.update { it.copy( localOnnxCustomModel = false, - localOnnxModels = it.localOnnxModels.withNewState( - it.localOnnxModels.find { m -> m.id == LocalAiModel.CustomOnnx.id }!!.copy( - selected = false, - ), - ), + scannedOnnxCustomModels = emptyList(), ) } setupButton .assertIsDisplayed() .assertIsNotEnabled() - .assertTextEquals("Setup") + .assertTextEquals("Start") switch .assertIsDisplayed() .assertIsOff() diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupViewModelTest.kt similarity index 84% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupViewModelTest.kt index 8713466db..1157cc38e 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/setup/ServerSetupViewModelTest.kt @@ -1,30 +1,33 @@ -package com.shifthackz.aisdv1.presentation.screen.setup - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.validation.common.CommonStringValidator -import com.shifthackz.aisdv1.core.validation.path.FilePathValidator -import com.shifthackz.aisdv1.core.validation.url.UrlValidator -import com.shifthackz.aisdv1.domain.entity.Configuration -import com.shifthackz.aisdv1.domain.entity.DownloadState -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.interactor.settings.SetupConnectionInterActor -import com.shifthackz.aisdv1.domain.interactor.wakelock.WakeLockInterActor -import com.shifthackz.aisdv1.domain.preference.PreferenceManager -import com.shifthackz.aisdv1.domain.usecase.downloadable.DeleteModelUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.DownloadModelUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase -import com.shifthackz.aisdv1.domain.usecase.settings.GetConfigurationUseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.mocks.mockHuggingFaceModels -import com.shifthackz.aisdv1.presentation.mocks.mockLocalAiModels -import com.shifthackz.aisdv1.presentation.mocks.mockServerSetupStateLocalModel -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +package dev.minios.pdaiv1.presentation.screen.setup + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.validation.common.CommonStringValidator +import dev.minios.pdaiv1.core.validation.path.FilePathValidator +import dev.minios.pdaiv1.core.validation.url.UrlValidator +import dev.minios.pdaiv1.domain.entity.Configuration +import dev.minios.pdaiv1.domain.entity.DownloadState +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.interactor.settings.SetupConnectionInterActor +import dev.minios.pdaiv1.domain.interactor.wakelock.WakeLockInterActor +import dev.minios.pdaiv1.domain.preference.PreferenceManager +import dev.minios.pdaiv1.domain.usecase.downloadable.DeleteModelUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.DownloadModelUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.GetLocalQnnModelsUseCase +import dev.minios.pdaiv1.domain.usecase.downloadable.ScanCustomModelsUseCase +import dev.minios.pdaiv1.domain.usecase.huggingface.FetchAndGetHuggingFaceModelsUseCase +import dev.minios.pdaiv1.domain.repository.FalAiEndpointRepository +import dev.minios.pdaiv1.domain.usecase.settings.GetConfigurationUseCase +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.mocks.mockHuggingFaceModels +import dev.minios.pdaiv1.presentation.mocks.mockLocalAiModels +import dev.minios.pdaiv1.presentation.mocks.mockServerSetupStateLocalModel +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll @@ -55,6 +58,9 @@ class ServerSetupViewModelTest : CoreViewModelTest() { private val stubPreferenceManager = mockk() private val stubWakeLockInterActor = mockk() private val stubMainRouter = mockk() + private val stubGetLocalQnnModelsUseCase = mockk() + private val stubFalAiEndpointRepository = mockk() + private val stubScanCustomModelsUseCase = mockk() override fun initializeViewModel() = ServerSetupViewModel( launchSource = LaunchSource.SETTINGS, @@ -74,6 +80,9 @@ class ServerSetupViewModelTest : CoreViewModelTest() { wakeLockInterActor = stubWakeLockInterActor, mainRouter = stubMainRouter, buildInfoProvider = BuildInfoProvider.stub, + getLocalQnnModelsUseCase = stubGetLocalQnnModelsUseCase, + falAiEndpointRepository = stubFalAiEndpointRepository, + scanCustomModelsUseCase = stubScanCustomModelsUseCase, ) @Before @@ -95,6 +104,14 @@ class ServerSetupViewModelTest : CoreViewModelTest() { every { stubFetchAndGetHuggingFaceModelsUseCase() } returns Single.just(mockHuggingFaceModels) + + every { + stubGetLocalQnnModelsUseCase() + } returns Single.just(emptyList()) + + every { + stubFalAiEndpointRepository.getAll() + } returns Single.just(emptyList()) } @After diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/splash/SplashViewModelTest.kt similarity index 80% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/splash/SplashViewModelTest.kt index a7f7bdd92..c91a06265 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/splash/SplashViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/splash/SplashViewModelTest.kt @@ -1,12 +1,12 @@ -package com.shifthackz.aisdv1.presentation.screen.splash - -import com.shifthackz.aisdv1.domain.usecase.splash.SplashNavigationUseCase -import com.shifthackz.aisdv1.presentation.core.CoreViewModelInitializeStrategy -import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider -import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider +package dev.minios.pdaiv1.presentation.screen.splash + +import dev.minios.pdaiv1.domain.usecase.splash.SplashNavigationUseCase +import dev.minios.pdaiv1.presentation.core.CoreViewModelInitializeStrategy +import dev.minios.pdaiv1.presentation.core.CoreViewModelTest +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.navigation.router.main.MainRouter +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider +import dev.minios.pdaiv1.presentation.stub.stubSchedulersProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModelTest.kt b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageViewModelTest.kt similarity index 92% rename from presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModelTest.kt rename to presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageViewModelTest.kt index 3b460ed54..1bf0c8f01 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModelTest.kt +++ b/presentation/src/test/java/dev/minios/pdaiv1/presentation/screen/txt2img/TextToImageViewModelTest.kt @@ -1,24 +1,24 @@ -package com.shifthackz.aisdv1.presentation.screen.txt2img - -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider -import com.shifthackz.aisdv1.core.validation.ValidationResult -import com.shifthackz.aisdv1.core.validation.dimension.DimensionValidator.Error -import com.shifthackz.aisdv1.domain.entity.OpenAiModel -import com.shifthackz.aisdv1.domain.entity.OpenAiQuality -import com.shifthackz.aisdv1.domain.entity.OpenAiSize -import com.shifthackz.aisdv1.domain.entity.OpenAiStyle -import com.shifthackz.aisdv1.domain.entity.ServerSource -import com.shifthackz.aisdv1.domain.entity.Settings -import com.shifthackz.aisdv1.domain.entity.StableDiffusionSampler -import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase -import com.shifthackz.aisdv1.presentation.core.CoreGenerationMviViewModelTest -import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent -import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent -import com.shifthackz.aisdv1.presentation.mocks.mockAiGenerationResult -import com.shifthackz.aisdv1.presentation.model.LaunchSource -import com.shifthackz.aisdv1.presentation.model.Modal -import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent -import com.shifthackz.aisdv1.presentation.stub.stubDispatchersProvider +package dev.minios.pdaiv1.presentation.screen.txt2img + +import dev.minios.pdaiv1.core.common.appbuild.BuildInfoProvider +import dev.minios.pdaiv1.core.validation.ValidationResult +import dev.minios.pdaiv1.core.validation.dimension.DimensionValidator.Error +import dev.minios.pdaiv1.domain.entity.OpenAiModel +import dev.minios.pdaiv1.domain.entity.OpenAiQuality +import dev.minios.pdaiv1.domain.entity.OpenAiSize +import dev.minios.pdaiv1.domain.entity.OpenAiStyle +import dev.minios.pdaiv1.domain.entity.ServerSource +import dev.minios.pdaiv1.domain.entity.Settings +import dev.minios.pdaiv1.domain.entity.StableDiffusionSampler +import dev.minios.pdaiv1.domain.usecase.generation.TextToImageUseCase +import dev.minios.pdaiv1.presentation.core.CoreGenerationMviViewModelTest +import dev.minios.pdaiv1.presentation.core.GenerationFormUpdateEvent +import dev.minios.pdaiv1.presentation.core.GenerationMviIntent +import dev.minios.pdaiv1.presentation.mocks.mockAiGenerationResult +import dev.minios.pdaiv1.presentation.model.LaunchSource +import dev.minios.pdaiv1.presentation.model.Modal +import dev.minios.pdaiv1.presentation.screen.drawer.DrawerIntent +import dev.minios.pdaiv1.presentation.stub.stubDispatchersProvider import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll @@ -54,11 +54,12 @@ class TextToImageViewModelTest : CoreGenerationMviViewModelTest private val stubFetchAndGetStabilityAiEnginesUseCase = mockk() private val stubFetchAndGetHuggingFaceModelsUseCase = mockk() private val stubFetchAndGetSwarmUiModelsUseCase = mockk() + private val stubGetLocalMediaPipeModelsUseCase = mockk() + private val stubGetLocalQnnModelsUseCase = mockk() + private val stubScanCustomModelsUseCase = mockk() override fun initializeViewModel() = EngineSelectionViewModel( dispatchersProvider = stubDispatchersProvider, @@ -62,6 +68,9 @@ class EngineSelectionViewModelTest : CoreViewModelTest fetchAndGetStabilityAiEnginesUseCase = stubFetchAndGetStabilityAiEnginesUseCase, getHuggingFaceModelsUseCase = stubFetchAndGetHuggingFaceModelsUseCase, fetchAndGetSwarmUiModelsUseCase = stubFetchAndGetSwarmUiModelsUseCase, + getLocalMediaPipeModelsUseCase = stubGetLocalMediaPipeModelsUseCase, + getLocalQnnModelsUseCase = stubGetLocalQnnModelsUseCase, + scanCustomModelsUseCase = stubScanCustomModelsUseCase, ) @Before @@ -89,6 +98,14 @@ class EngineSelectionViewModelTest : CoreViewModelTest onFailure = { t -> Flowable.error(t) }, ) } + + every { + stubGetLocalQnnModelsUseCase() + } returns Single.just(emptyList()) + + every { + stubGetLocalMediaPipeModelsUseCase() + } returns Single.just(emptyList()) } @After diff --git a/scripts/prepare_qnn_libs.sh b/scripts/prepare_qnn_libs.sh new file mode 100755 index 000000000..28a995e26 --- /dev/null +++ b/scripts/prepare_qnn_libs.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# Script to prepare QNN libraries from LocalDream APK +# +# Usage: ./scripts/prepare_qnn_libs.sh +# +# This script downloads the LocalDream APK and extracts: +# - libstable_diffusion_core.so (main native library) +# - QNN libraries (libQnnHtp*.so, libQnnSystem.so) +# - Base MNN models (clip, tokenizer, unet, vae) + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +QNN_MODULE_DIR="$PROJECT_DIR/feature/qnn/src/main" + +# GitHub repository info +REPO_OWNER="xororz" +REPO_NAME="local-dream" +APK_PATTERN="LocalDream_armv8a_.*\.apk" + +APK_FILE="/tmp/LocalDream.apk" +EXTRACT_DIR="/tmp/localdream_extracted" + +echo "============================================" +echo " QNN Libraries Preparation Script" +echo "============================================" +echo "" + +# Fetch latest release info from GitHub API +echo "[0/6] Fetching latest release info..." +LATEST_RELEASE_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest" +RELEASE_INFO=$(curl -s "$LATEST_RELEASE_URL") + +# Extract APK download URL +APK_URL=$(echo "$RELEASE_INFO" | grep -o "https://.*${APK_PATTERN}" | head -1) +RELEASE_TAG=$(echo "$RELEASE_INFO" | grep '"tag_name"' | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') + +if [ -z "$APK_URL" ]; then + echo "ERROR: Could not find APK download URL in latest release" + echo "Please check: https://github.com/$REPO_OWNER/$REPO_NAME/releases/latest" + exit 1 +fi + +echo " Latest release: $RELEASE_TAG" +echo " APK URL: $APK_URL" +echo "" + +# Check if libraries already exist +if [ -f "$QNN_MODULE_DIR/jniLibs/arm64-v8a/libstable_diffusion_core.so" ]; then + echo "Libraries already exist. Delete them first to re-download." + echo " rm -rf $QNN_MODULE_DIR/jniLibs/arm64-v8a/*.so" + echo " rm -rf $QNN_MODULE_DIR/assets/qnnlibs/*.so" + exit 0 +fi + +# Download APK +echo "[1/6] Downloading LocalDream APK..." +if [ -f "$APK_FILE" ]; then + echo " APK already downloaded, skipping..." +else + curl -L -o "$APK_FILE" "$APK_URL" + echo " Downloaded $(du -h "$APK_FILE" | cut -f1)" +fi + +# Extract APK +echo "[2/6] Extracting APK..." +rm -rf "$EXTRACT_DIR" +unzip -q -o "$APK_FILE" -d "$EXTRACT_DIR" +echo " Extracted to $EXTRACT_DIR" + +# Copy native library +echo "[3/6] Copying native library..." +mkdir -p "$QNN_MODULE_DIR/jniLibs/arm64-v8a" +cp "$EXTRACT_DIR/lib/arm64-v8a/libstable_diffusion_core.so" \ + "$QNN_MODULE_DIR/jniLibs/arm64-v8a/" +echo " Copied libstable_diffusion_core.so ($(du -h "$QNN_MODULE_DIR/jniLibs/arm64-v8a/libstable_diffusion_core.so" | cut -f1))" + +# Copy QNN libraries +echo "[4/6] Copying QNN libraries..." +mkdir -p "$QNN_MODULE_DIR/assets/qnnlibs" +cp "$EXTRACT_DIR/assets/qnnlibs/"*.so "$QNN_MODULE_DIR/assets/qnnlibs/" +QNN_COUNT=$(ls -1 "$QNN_MODULE_DIR/assets/qnnlibs/"*.so 2>/dev/null | wc -l) +QNN_SIZE=$(du -sh "$QNN_MODULE_DIR/assets/qnnlibs" | cut -f1) +echo " Copied $QNN_COUNT QNN libraries ($QNN_SIZE)" + +# Copy base models +echo "[5/6] Copying base models..." +mkdir -p "$QNN_MODULE_DIR/assets/cvtbase" +cp "$EXTRACT_DIR/assets/cvtbase/"* "$QNN_MODULE_DIR/assets/cvtbase/" +MODELS_SIZE=$(du -sh "$QNN_MODULE_DIR/assets/cvtbase" | cut -f1) +echo " Copied base models ($MODELS_SIZE)" + +# Cleanup +echo "" +echo "[6/6] Cleaning up..." +rm -rf "$EXTRACT_DIR" +# Keep APK for potential re-extraction +echo " Kept APK at $APK_FILE for future use" + +# Summary +echo "" +echo "============================================" +echo " Done!" +echo "============================================" +echo "" +echo "Libraries prepared in:" +echo " $QNN_MODULE_DIR/jniLibs/arm64-v8a/" +echo " $QNN_MODULE_DIR/assets/qnnlibs/" +echo " $QNN_MODULE_DIR/assets/cvtbase/" +echo "" +echo "You can now build the project with:" +echo " ./gradlew :app:assembleFullDebug" +echo "" +echo "NOTE: These files are NOT committed to git." +echo " Each developer must run this script." diff --git a/settings.gradle.kts b/settings.gradle.kts index fca65518b..c75b96d32 100755 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,6 +36,7 @@ val modules = listOf( ":feature:auth", ":feature:diffusion", ":feature:mediapipe", + ":feature:qnn", ":feature:work", ":network", ":presentation", diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 18843b63c..c5f461b3d 100755 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } android { - namespace = "com.shifthackz.aisdv1.storage" + namespace = "dev.minios.pdaiv1.storage" defaultConfig { ksp { arg("room.incremental", "true") diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.cache.CacheDatabase/1.json b/storage/schemas/dev.minios.pdaiv1.storage.db.cache.CacheDatabase/1.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.cache.CacheDatabase/1.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.cache.CacheDatabase/1.json diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/1.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/1.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/1.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/1.json diff --git a/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/10.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/10.json new file mode 100644 index 000000000..67a4cac4b --- /dev/null +++ b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/10.json @@ -0,0 +1,385 @@ +{ + "formatVersion": 1, + "database": { + "version": 10, + "identityHash": "028b5ecb18e9d8e60fd31ee372744b2d", + "entities": [ + { + "tableName": "generation_results", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `image_base_64` TEXT NOT NULL, `original_image_base_64` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `generation_type` TEXT NOT NULL, `prompt` TEXT NOT NULL, `negative_prompt` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `sampling_steps` INTEGER NOT NULL, `cfg_scale` REAL NOT NULL, `restore_faces` INTEGER NOT NULL, `sampler` TEXT NOT NULL, `seed` TEXT NOT NULL, `sub_seed` TEXT NOT NULL DEFAULT '', `sub_seed_strength` REAL NOT NULL DEFAULT 0.0, `denoising_strength` REAL NOT NULL DEFAULT 0.0, `hidden` INTEGER NOT NULL DEFAULT 0, `media_path` TEXT NOT NULL DEFAULT '', `input_media_path` TEXT NOT NULL DEFAULT '', `media_type` TEXT NOT NULL DEFAULT 'IMAGE', `model_name` TEXT NOT NULL DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageBase64", + "columnName": "image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalImageBase64", + "columnName": "original_image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "generationType", + "columnName": "generation_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "prompt", + "columnName": "prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "negativePrompt", + "columnName": "negative_prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "samplingSteps", + "columnName": "sampling_steps", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cfgScale", + "columnName": "cfg_scale", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "restoreFaces", + "columnName": "restore_faces", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sampler", + "columnName": "sampler", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seed", + "columnName": "seed", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subSeed", + "columnName": "sub_seed", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "subSeedStrength", + "columnName": "sub_seed_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "denoisingStrength", + "columnName": "denoising_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "mediaPath", + "columnName": "media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "inputMediaPath", + "columnName": "input_media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mediaType", + "columnName": "media_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'IMAGE'" + }, + { + "fieldPath": "modelName", + "columnName": "model_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "local_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT NOT NULL DEFAULT 'onnx', `name` TEXT NOT NULL, `size` TEXT NOT NULL, `sources` TEXT NOT NULL, `chipset_suffix` TEXT DEFAULT NULL, `run_on_cpu` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'onnx'" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sources", + "columnName": "sources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chipsetSuffix", + "columnName": "chipset_suffix", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "runOnCpu", + "columnName": "run_on_cpu", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "hugging_face_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `alias` TEXT NOT NULL, `source` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "alias", + "columnName": "alias", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "supporters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `message` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "fal_ai_endpoints", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `endpoint_id` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `category` TEXT NOT NULL, `group_name` TEXT NOT NULL DEFAULT 'Custom', `thumbnail_url` TEXT NOT NULL, `playground_url` TEXT NOT NULL, `documentation_url` TEXT NOT NULL, `is_custom` INTEGER NOT NULL DEFAULT 1, `schema_json` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpoint_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Custom'" + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playgroundUrl", + "columnName": "playground_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationUrl", + "columnName": "documentation_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isCustom", + "columnName": "is_custom", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "schemaJson", + "columnName": "schema_json", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '028b5ecb18e9d8e60fd31ee372744b2d')" + ] + } +} \ No newline at end of file diff --git a/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/11.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/11.json new file mode 100644 index 000000000..c3710681a --- /dev/null +++ b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/11.json @@ -0,0 +1,392 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "bd5d3206e9a1a5ba4f509fae3727cf71", + "entities": [ + { + "tableName": "generation_results", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `image_base_64` TEXT NOT NULL, `original_image_base_64` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `generation_type` TEXT NOT NULL, `prompt` TEXT NOT NULL, `negative_prompt` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `sampling_steps` INTEGER NOT NULL, `cfg_scale` REAL NOT NULL, `restore_faces` INTEGER NOT NULL, `sampler` TEXT NOT NULL, `seed` TEXT NOT NULL, `sub_seed` TEXT NOT NULL DEFAULT '', `sub_seed_strength` REAL NOT NULL DEFAULT 0.0, `denoising_strength` REAL NOT NULL DEFAULT 0.0, `hidden` INTEGER NOT NULL DEFAULT 0, `media_path` TEXT NOT NULL DEFAULT '', `input_media_path` TEXT NOT NULL DEFAULT '', `media_type` TEXT NOT NULL DEFAULT 'IMAGE', `model_name` TEXT NOT NULL DEFAULT '', `blur_hash` TEXT NOT NULL DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageBase64", + "columnName": "image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalImageBase64", + "columnName": "original_image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "generationType", + "columnName": "generation_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "prompt", + "columnName": "prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "negativePrompt", + "columnName": "negative_prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "samplingSteps", + "columnName": "sampling_steps", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cfgScale", + "columnName": "cfg_scale", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "restoreFaces", + "columnName": "restore_faces", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sampler", + "columnName": "sampler", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seed", + "columnName": "seed", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subSeed", + "columnName": "sub_seed", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "subSeedStrength", + "columnName": "sub_seed_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "denoisingStrength", + "columnName": "denoising_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "mediaPath", + "columnName": "media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "inputMediaPath", + "columnName": "input_media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mediaType", + "columnName": "media_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'IMAGE'" + }, + { + "fieldPath": "modelName", + "columnName": "model_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "blurHash", + "columnName": "blur_hash", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "local_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT NOT NULL DEFAULT 'onnx', `name` TEXT NOT NULL, `size` TEXT NOT NULL, `sources` TEXT NOT NULL, `chipset_suffix` TEXT DEFAULT NULL, `run_on_cpu` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'onnx'" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sources", + "columnName": "sources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chipsetSuffix", + "columnName": "chipset_suffix", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "runOnCpu", + "columnName": "run_on_cpu", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "hugging_face_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `alias` TEXT NOT NULL, `source` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "alias", + "columnName": "alias", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "supporters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `message` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "fal_ai_endpoints", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `endpoint_id` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `category` TEXT NOT NULL, `group_name` TEXT NOT NULL DEFAULT 'Custom', `thumbnail_url` TEXT NOT NULL, `playground_url` TEXT NOT NULL, `documentation_url` TEXT NOT NULL, `is_custom` INTEGER NOT NULL DEFAULT 1, `schema_json` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpoint_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Custom'" + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playgroundUrl", + "columnName": "playground_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationUrl", + "columnName": "documentation_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isCustom", + "columnName": "is_custom", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "schemaJson", + "columnName": "schema_json", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bd5d3206e9a1a5ba4f509fae3727cf71')" + ] + } +} \ No newline at end of file diff --git a/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/12.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/12.json new file mode 100644 index 000000000..f8e247b3a --- /dev/null +++ b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/12.json @@ -0,0 +1,399 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "d52185a1718e5beef2afd27cc534e240", + "entities": [ + { + "tableName": "generation_results", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `image_base_64` TEXT NOT NULL, `original_image_base_64` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `generation_type` TEXT NOT NULL, `prompt` TEXT NOT NULL, `negative_prompt` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `sampling_steps` INTEGER NOT NULL, `cfg_scale` REAL NOT NULL, `restore_faces` INTEGER NOT NULL, `sampler` TEXT NOT NULL, `seed` TEXT NOT NULL, `sub_seed` TEXT NOT NULL DEFAULT '', `sub_seed_strength` REAL NOT NULL DEFAULT 0.0, `denoising_strength` REAL NOT NULL DEFAULT 0.0, `hidden` INTEGER NOT NULL DEFAULT 0, `liked` INTEGER NOT NULL DEFAULT 0, `media_path` TEXT NOT NULL DEFAULT '', `input_media_path` TEXT NOT NULL DEFAULT '', `media_type` TEXT NOT NULL DEFAULT 'IMAGE', `model_name` TEXT NOT NULL DEFAULT '', `blur_hash` TEXT NOT NULL DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageBase64", + "columnName": "image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalImageBase64", + "columnName": "original_image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "generationType", + "columnName": "generation_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "prompt", + "columnName": "prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "negativePrompt", + "columnName": "negative_prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "samplingSteps", + "columnName": "sampling_steps", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cfgScale", + "columnName": "cfg_scale", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "restoreFaces", + "columnName": "restore_faces", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sampler", + "columnName": "sampler", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seed", + "columnName": "seed", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subSeed", + "columnName": "sub_seed", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "subSeedStrength", + "columnName": "sub_seed_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "denoisingStrength", + "columnName": "denoising_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "mediaPath", + "columnName": "media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "inputMediaPath", + "columnName": "input_media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mediaType", + "columnName": "media_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'IMAGE'" + }, + { + "fieldPath": "modelName", + "columnName": "model_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "blurHash", + "columnName": "blur_hash", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "local_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT NOT NULL DEFAULT 'onnx', `name` TEXT NOT NULL, `size` TEXT NOT NULL, `sources` TEXT NOT NULL, `chipset_suffix` TEXT DEFAULT NULL, `run_on_cpu` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'onnx'" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sources", + "columnName": "sources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chipsetSuffix", + "columnName": "chipset_suffix", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "runOnCpu", + "columnName": "run_on_cpu", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "hugging_face_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `alias` TEXT NOT NULL, `source` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "alias", + "columnName": "alias", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "supporters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `message` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "fal_ai_endpoints", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `endpoint_id` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `category` TEXT NOT NULL, `group_name` TEXT NOT NULL DEFAULT 'Custom', `thumbnail_url` TEXT NOT NULL, `playground_url` TEXT NOT NULL, `documentation_url` TEXT NOT NULL, `is_custom` INTEGER NOT NULL DEFAULT 1, `schema_json` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpoint_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Custom'" + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playgroundUrl", + "columnName": "playground_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationUrl", + "columnName": "documentation_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isCustom", + "columnName": "is_custom", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "schemaJson", + "columnName": "schema_json", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd52185a1718e5beef2afd27cc534e240')" + ] + } +} \ No newline at end of file diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/2.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/2.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/2.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/2.json diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/3.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/3.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/3.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/3.json diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/4.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/4.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/4.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/4.json diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/5.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/5.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/5.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/5.json diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/6.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/6.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/6.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/6.json diff --git a/storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/7.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/7.json similarity index 100% rename from storage/schemas/com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase/7.json rename to storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/7.json diff --git a/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/8.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/8.json new file mode 100644 index 000000000..9e839ddf3 --- /dev/null +++ b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/8.json @@ -0,0 +1,364 @@ +{ + "formatVersion": 1, + "database": { + "version": 8, + "identityHash": "b82abe60f453e0b87b11162f583f6aa7", + "entities": [ + { + "tableName": "generation_results", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `image_base_64` TEXT NOT NULL, `original_image_base_64` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `generation_type` TEXT NOT NULL, `prompt` TEXT NOT NULL, `negative_prompt` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `sampling_steps` INTEGER NOT NULL, `cfg_scale` REAL NOT NULL, `restore_faces` INTEGER NOT NULL, `sampler` TEXT NOT NULL, `seed` TEXT NOT NULL, `sub_seed` TEXT NOT NULL DEFAULT '', `sub_seed_strength` REAL NOT NULL DEFAULT 0.0, `denoising_strength` REAL NOT NULL DEFAULT 0.0, `hidden` INTEGER NOT NULL DEFAULT 0, `media_path` TEXT NOT NULL DEFAULT '', `input_media_path` TEXT NOT NULL DEFAULT '', `media_type` TEXT NOT NULL DEFAULT 'IMAGE')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageBase64", + "columnName": "image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalImageBase64", + "columnName": "original_image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "generationType", + "columnName": "generation_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "prompt", + "columnName": "prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "negativePrompt", + "columnName": "negative_prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "samplingSteps", + "columnName": "sampling_steps", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cfgScale", + "columnName": "cfg_scale", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "restoreFaces", + "columnName": "restore_faces", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sampler", + "columnName": "sampler", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seed", + "columnName": "seed", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subSeed", + "columnName": "sub_seed", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "subSeedStrength", + "columnName": "sub_seed_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "denoisingStrength", + "columnName": "denoising_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "mediaPath", + "columnName": "media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "inputMediaPath", + "columnName": "input_media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mediaType", + "columnName": "media_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'IMAGE'" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "local_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT NOT NULL DEFAULT 'onnx', `name` TEXT NOT NULL, `size` TEXT NOT NULL, `sources` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'onnx'" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sources", + "columnName": "sources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "hugging_face_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `alias` TEXT NOT NULL, `source` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "alias", + "columnName": "alias", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "supporters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `message` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "fal_ai_endpoints", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `endpoint_id` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `category` TEXT NOT NULL, `group_name` TEXT NOT NULL DEFAULT 'Custom', `thumbnail_url` TEXT NOT NULL, `playground_url` TEXT NOT NULL, `documentation_url` TEXT NOT NULL, `is_custom` INTEGER NOT NULL DEFAULT 1, `schema_json` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpoint_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Custom'" + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playgroundUrl", + "columnName": "playground_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationUrl", + "columnName": "documentation_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isCustom", + "columnName": "is_custom", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "schemaJson", + "columnName": "schema_json", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b82abe60f453e0b87b11162f583f6aa7')" + ] + } +} \ No newline at end of file diff --git a/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/9.json b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/9.json new file mode 100644 index 000000000..85372ae93 --- /dev/null +++ b/storage/schemas/dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase/9.json @@ -0,0 +1,371 @@ +{ + "formatVersion": 1, + "database": { + "version": 9, + "identityHash": "550fbbe25a0b7bb52d81913b3e2899d1", + "entities": [ + { + "tableName": "generation_results", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `image_base_64` TEXT NOT NULL, `original_image_base_64` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `generation_type` TEXT NOT NULL, `prompt` TEXT NOT NULL, `negative_prompt` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `sampling_steps` INTEGER NOT NULL, `cfg_scale` REAL NOT NULL, `restore_faces` INTEGER NOT NULL, `sampler` TEXT NOT NULL, `seed` TEXT NOT NULL, `sub_seed` TEXT NOT NULL DEFAULT '', `sub_seed_strength` REAL NOT NULL DEFAULT 0.0, `denoising_strength` REAL NOT NULL DEFAULT 0.0, `hidden` INTEGER NOT NULL DEFAULT 0, `media_path` TEXT NOT NULL DEFAULT '', `input_media_path` TEXT NOT NULL DEFAULT '', `media_type` TEXT NOT NULL DEFAULT 'IMAGE', `model_name` TEXT NOT NULL DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageBase64", + "columnName": "image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalImageBase64", + "columnName": "original_image_base_64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "generationType", + "columnName": "generation_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "prompt", + "columnName": "prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "negativePrompt", + "columnName": "negative_prompt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "samplingSteps", + "columnName": "sampling_steps", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cfgScale", + "columnName": "cfg_scale", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "restoreFaces", + "columnName": "restore_faces", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sampler", + "columnName": "sampler", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seed", + "columnName": "seed", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subSeed", + "columnName": "sub_seed", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "subSeedStrength", + "columnName": "sub_seed_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "denoisingStrength", + "columnName": "denoising_strength", + "affinity": "REAL", + "notNull": true, + "defaultValue": "0.0" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "mediaPath", + "columnName": "media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "inputMediaPath", + "columnName": "input_media_path", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "mediaType", + "columnName": "media_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'IMAGE'" + }, + { + "fieldPath": "modelName", + "columnName": "model_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "local_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT NOT NULL DEFAULT 'onnx', `name` TEXT NOT NULL, `size` TEXT NOT NULL, `sources` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'onnx'" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sources", + "columnName": "sources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "hugging_face_models", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `alias` TEXT NOT NULL, `source` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "alias", + "columnName": "alias", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "supporters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `message` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "fal_ai_endpoints", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `endpoint_id` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `category` TEXT NOT NULL, `group_name` TEXT NOT NULL DEFAULT 'Custom', `thumbnail_url` TEXT NOT NULL, `playground_url` TEXT NOT NULL, `documentation_url` TEXT NOT NULL, `is_custom` INTEGER NOT NULL DEFAULT 1, `schema_json` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endpointId", + "columnName": "endpoint_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group_name", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Custom'" + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playgroundUrl", + "columnName": "playground_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentationUrl", + "columnName": "documentation_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isCustom", + "columnName": "is_custom", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "schemaJson", + "columnName": "schema_json", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '550fbbe25a0b7bb52d81913b3e2899d1')" + ] + } +} \ No newline at end of file diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/CacheDatabase.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/CacheDatabase.kt deleted file mode 100755 index 3dc34f746..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/CacheDatabase.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache - -import androidx.room.Database -import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import com.shifthackz.aisdv1.storage.converters.ListConverters -import com.shifthackz.aisdv1.storage.converters.MapConverters -import com.shifthackz.aisdv1.storage.db.cache.CacheDatabase.Companion.DB_VERSION -import com.shifthackz.aisdv1.storage.db.cache.dao.ServerConfigurationDao -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionEmbeddingDao -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionHyperNetworkDao -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionLoraDao -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionModelDao -import com.shifthackz.aisdv1.storage.db.cache.dao.StableDiffusionSamplerDao -import com.shifthackz.aisdv1.storage.db.cache.dao.SwarmUiModelDao -import com.shifthackz.aisdv1.storage.db.cache.entity.ServerConfigurationEntity -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionLoraEntity -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionModelEntity -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionSamplerEntity -import com.shifthackz.aisdv1.storage.db.cache.entity.SwarmUiModelEntity - -@Database( - version = DB_VERSION, - exportSchema = true, - entities = [ - ServerConfigurationEntity::class, - StableDiffusionModelEntity::class, - StableDiffusionSamplerEntity::class, - StableDiffusionLoraEntity::class, - StableDiffusionHyperNetworkEntity::class, - StableDiffusionEmbeddingEntity::class, - SwarmUiModelEntity::class, - ], -) -@TypeConverters( - MapConverters::class, - ListConverters::class, -) -internal abstract class CacheDatabase : RoomDatabase() { - abstract fun serverConfigurationDao(): ServerConfigurationDao - abstract fun sdModelDao(): StableDiffusionModelDao - abstract fun sdSamplerDao(): StableDiffusionSamplerDao - abstract fun sdLoraDao(): StableDiffusionLoraDao - abstract fun sdHyperNetworkDao(): StableDiffusionHyperNetworkDao - abstract fun sdEmbeddingDao(): StableDiffusionEmbeddingDao - abstract fun swarmUiModelDao(): SwarmUiModelDao - - companion object { - const val DB_VERSION = 1 - } -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionEmbeddingContract.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionEmbeddingContract.kt deleted file mode 100644 index e4e1cc180..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionEmbeddingContract.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.contract - -object StableDiffusionEmbeddingContract { - const val TABLE = "embeddings" - - const val ID = "id" - const val KEYWORD = "keyword" -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/ServerConfigurationDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/ServerConfigurationDao.kt deleted file mode 100755 index b126618b6..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/ServerConfigurationDao.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.cache.contract.ServerConfigurationContract -import com.shifthackz.aisdv1.storage.db.cache.entity.ServerConfigurationEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface ServerConfigurationDao { - - @Query("SELECT * FROM ${ServerConfigurationContract.TABLE} LIMIT 1") - fun query(): Single - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(item: ServerConfigurationEntity): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionEmbeddingDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionEmbeddingDao.kt deleted file mode 100644 index a5323ed61..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionEmbeddingDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionEmbeddingContract -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface StableDiffusionEmbeddingDao { - - @Query("SELECT * FROM ${StableDiffusionEmbeddingContract.TABLE}") - fun queryAll(): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertList(items: List): Completable - - @Query("DELETE FROM ${StableDiffusionEmbeddingContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionHyperNetworkDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionHyperNetworkDao.kt deleted file mode 100644 index a29aafe1b..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionHyperNetworkDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionHyperNetworkContract -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface StableDiffusionHyperNetworkDao { - - @Query("SELECT * FROM ${StableDiffusionHyperNetworkContract.TABLE}") - fun queryAll(): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertList(items: List): Completable - - @Query("DELETE FROM ${StableDiffusionHyperNetworkContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionLoraDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionLoraDao.kt deleted file mode 100644 index b7efb3cf8..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionLoraDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionLoraContract -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionLoraEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface StableDiffusionLoraDao { - - @Query("SELECT * FROM ${StableDiffusionLoraContract.TABLE}") - fun queryAll(): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertList(items: List): Completable - - @Query("DELETE FROM ${StableDiffusionLoraContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionModelDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionModelDao.kt deleted file mode 100755 index 6d76581a8..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionModelDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionModelContract -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionModelEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface StableDiffusionModelDao { - - @Query("SELECT * FROM ${StableDiffusionModelContract.TABLE}") - fun queryAll(): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertList(items: List): Completable - - @Query("DELETE FROM ${StableDiffusionModelContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionSamplerDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionSamplerDao.kt deleted file mode 100755 index 81fd07db8..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/StableDiffusionSamplerDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionSamplerContract -import com.shifthackz.aisdv1.storage.db.cache.entity.StableDiffusionSamplerEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface StableDiffusionSamplerDao { - - @Query("SELECT * FROM ${StableDiffusionSamplerContract.TABLE}") - fun queryAll(): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertList(items: List): Completable - - @Query("DELETE FROM ${StableDiffusionSamplerContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/SwarmUiModelDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/SwarmUiModelDao.kt deleted file mode 100644 index bda49f52e..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/dao/SwarmUiModelDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.cache.contract.SwarmUiModelContract -import com.shifthackz.aisdv1.storage.db.cache.entity.SwarmUiModelEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface SwarmUiModelDao { - - @Query("SELECT * FROM ${SwarmUiModelContract.TABLE}") - fun queryAll(): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertList(items: List): Completable - - @Query("DELETE FROM ${SwarmUiModelContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionEmbeddingEntity.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionEmbeddingEntity.kt deleted file mode 100644 index b0d41cf03..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionEmbeddingEntity.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.cache.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionEmbeddingContract - -@Entity(tableName = StableDiffusionEmbeddingContract.TABLE) -data class StableDiffusionEmbeddingEntity( - @PrimaryKey(autoGenerate = false) - @ColumnInfo(name = StableDiffusionEmbeddingContract.ID) - val id: String, - @ColumnInfo(name = StableDiffusionEmbeddingContract.KEYWORD) - val keyword: String, -) diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/PersistentDatabase.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/PersistentDatabase.kt deleted file mode 100644 index b5656278a..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/PersistentDatabase.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.persistent - -import androidx.room.AutoMigration -import androidx.room.Database -import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import com.shifthackz.aisdv1.storage.converters.* -import com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase.Companion.DB_VERSION -import com.shifthackz.aisdv1.storage.db.persistent.contract.* -import com.shifthackz.aisdv1.storage.db.persistent.dao.* -import com.shifthackz.aisdv1.storage.db.persistent.entity.* - -@Database( - version = DB_VERSION, - exportSchema = true, - entities = [ - GenerationResultEntity::class, - LocalModelEntity::class, - HuggingFaceModelEntity::class, - SupporterEntity::class, - ], - autoMigrations = [ - /** - * Added 3 fields to [GenerationResultEntity]: - * - [GenerationResultContract.SUB_SEED] - * - [GenerationResultContract.SUB_SEED_STRENGTH] - * - [GenerationResultContract.DENOISING_STRENGTH] - */ - AutoMigration(from = 1, to = 2), - /** - * Added [LocalModelEntity]. - */ - AutoMigration(from = 2, to = 3), - /** - * Added [HuggingFaceModelEntity]. - */ - AutoMigration(from = 3, to = 4), - /** - * Added [SupporterEntity]. - */ - AutoMigration(from = 4, to = 5), - /** - * Added 1 field to [LocalModelEntity]: - * - [LocalModelContract.TYPE] - */ - AutoMigration(from = 5, to = 6), - /** - * Added 1 field to [GenerationResultEntity]: - * - [GenerationResultContract.HIDDEN] - */ - AutoMigration(from = 6, to = 7), - ], -) -@TypeConverters( - DateConverters::class, - ListConverters::class, -) -internal abstract class PersistentDatabase : RoomDatabase() { - abstract fun generationResultDao(): GenerationResultDao - abstract fun localModelDao(): LocalModelDao - abstract fun huggingFaceModelDao(): HuggingFaceModelDao - abstract fun supporterDao(): SupporterDao - - companion object { - const val DB_NAME = "ai_sd_v1_storage_db" - const val DB_VERSION = 7 - } -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/GenerationResultContract.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/GenerationResultContract.kt deleted file mode 100644 index 9bfed4a8f..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/GenerationResultContract.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.persistent.contract - -internal object GenerationResultContract { - const val TABLE = "generation_results" - - const val ID = "id" - const val IMAGE_BASE_64 = "image_base_64" - const val ORIGINAL_IMAGE_BASE_64 = "original_image_base_64" - const val CREATED_AT = "created_at" - const val GENERATION_TYPE = "generation_type" - - const val PROMPT = "prompt" - const val NEGATIVE_PROMPT = "negative_prompt" - const val WIDTH = "width" - const val HEIGHT = "height" - const val SAMPLING_STEPS = "sampling_steps" - const val CFG_SCALE = "cfg_scale" - const val RESTORE_FACES = "restore_faces" - const val SAMPLER = "sampler" - const val SEED = "seed" - const val SUB_SEED = "sub_seed" - const val SUB_SEED_STRENGTH = "sub_seed_strength" - const val DENOISING_STRENGTH = "denoising_strength" - const val HIDDEN = "hidden" -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/LocalModelContract.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/LocalModelContract.kt deleted file mode 100644 index 2ffebf43e..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/LocalModelContract.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.persistent.contract - -object LocalModelContract { - const val TABLE = "local_models" - - const val ID = "id" - const val TYPE = "type" - const val NAME = "name" - const val SIZE = "size" - const val SOURCES = "sources" -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/GenerationResultDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/GenerationResultDao.kt deleted file mode 100644 index 8b39a965d..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/GenerationResultDao.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.persistent.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.persistent.contract.GenerationResultContract -import com.shifthackz.aisdv1.storage.db.persistent.entity.GenerationResultEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface GenerationResultDao { - - @Query("SELECT * FROM ${GenerationResultContract.TABLE} ORDER BY ${GenerationResultContract.CREATED_AT} DESC") - fun query(): Single> - - @Query("SELECT * FROM ${GenerationResultContract.TABLE} ORDER BY ${GenerationResultContract.CREATED_AT} DESC LIMIT :limit OFFSET :offset ") - fun queryPage(limit: Int, offset: Int): Single> - - @Query("SELECT * FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} = :id LIMIT 1") - fun queryById(id: Long): Single - - @Query("SELECT * FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} IN (:idList)") - fun queryByIdList(idList: List): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(item: GenerationResultEntity): Single - - @Query("DELETE FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} = :id") - fun deleteById(id: Long): Completable - - @Query("DELETE FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} IN (:idList)") - fun deleteByIdList(idList: List): Completable - - @Query("DELETE FROM ${GenerationResultContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/SupporterDao.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/SupporterDao.kt deleted file mode 100644 index 58846287d..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/SupporterDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.persistent.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.persistent.contract.SupporterContract -import com.shifthackz.aisdv1.storage.db.persistent.entity.SupporterEntity -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single - -@Dao -interface SupporterDao { - - @Query("SELECT * FROM ${SupporterContract.TABLE} ORDER BY ${SupporterContract.DATE} DESC") - fun queryAll(): Single> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertList(items: List): Completable - - @Query("DELETE FROM ${SupporterContract.TABLE}") - fun deleteAll(): Completable -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/GenerationResultEntity.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/GenerationResultEntity.kt deleted file mode 100644 index 80fca4c1b..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/GenerationResultEntity.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.persistent.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.persistent.contract.GenerationResultContract -import java.util.Date - -@Entity(tableName = GenerationResultContract.TABLE) -data class GenerationResultEntity( - @PrimaryKey(autoGenerate = true) - @ColumnInfo(name = GenerationResultContract.ID) - val id: Long, - @ColumnInfo(name = GenerationResultContract.IMAGE_BASE_64) - val imageBase64: String, - @ColumnInfo(name = GenerationResultContract.ORIGINAL_IMAGE_BASE_64) - val originalImageBase64: String, - @ColumnInfo(name = GenerationResultContract.CREATED_AT) - val createdAt: Date, - @ColumnInfo(name = GenerationResultContract.GENERATION_TYPE) - val generationType: String, - @ColumnInfo(name = GenerationResultContract.PROMPT) - val prompt: String, - @ColumnInfo(name = GenerationResultContract.NEGATIVE_PROMPT) - val negativePrompt: String, - @ColumnInfo(name = GenerationResultContract.WIDTH) - val width: Int, - @ColumnInfo(name = GenerationResultContract.HEIGHT) - val height: Int, - @ColumnInfo(name = GenerationResultContract.SAMPLING_STEPS) - val samplingSteps: Int, - @ColumnInfo(name = GenerationResultContract.CFG_SCALE) - val cfgScale: Float, - @ColumnInfo(name = GenerationResultContract.RESTORE_FACES) - val restoreFaces: Boolean, - @ColumnInfo(name = GenerationResultContract.SAMPLER) - val sampler: String, - @ColumnInfo(name = GenerationResultContract.SEED) - val seed: String, - @ColumnInfo(name = GenerationResultContract.SUB_SEED, defaultValue = "") - val subSeed: String, - @ColumnInfo(name = GenerationResultContract.SUB_SEED_STRENGTH, defaultValue = "${0f}") - val subSeedStrength: Float, - @ColumnInfo(name = GenerationResultContract.DENOISING_STRENGTH, defaultValue = "${0f}") - val denoisingStrength: Float, - @ColumnInfo(name = GenerationResultContract.HIDDEN, defaultValue = "0") - val hidden: Boolean, -) diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/LocalModelEntity.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/LocalModelEntity.kt deleted file mode 100644 index d69856d25..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/LocalModelEntity.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.shifthackz.aisdv1.storage.db.persistent.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.persistent.contract.LocalModelContract - -@Entity(tableName = LocalModelContract.TABLE) -data class LocalModelEntity( - @PrimaryKey(autoGenerate = false) - @ColumnInfo(name = LocalModelContract.ID) - val id: String, - @ColumnInfo(name = LocalModelContract.TYPE, defaultValue = "onnx") - val type: String, - @ColumnInfo(name = LocalModelContract.NAME) - val name: String, - @ColumnInfo(name = LocalModelContract.SIZE) - val size: String, - @ColumnInfo(name = LocalModelContract.SOURCES) - val sources: List, -) diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/gateway/GatewayClearCacheDb.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/gateway/GatewayClearCacheDb.kt deleted file mode 100644 index bf9c48b01..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/gateway/GatewayClearCacheDb.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.storage.gateway - -fun interface GatewayClearCacheDb { - operator fun invoke() -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/gateway/GatewayClearPersistentDb.kt b/storage/src/main/java/com/shifthackz/aisdv1/storage/gateway/GatewayClearPersistentDb.kt deleted file mode 100644 index dc8b49611..000000000 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/gateway/GatewayClearPersistentDb.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.shifthackz.aisdv1.storage.gateway - -fun interface GatewayClearPersistentDb { - operator fun invoke() -} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/converters/DateConverters.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/converters/DateConverters.kt similarity index 82% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/converters/DateConverters.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/converters/DateConverters.kt index 703845c8d..8b07f7780 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/converters/DateConverters.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/converters/DateConverters.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.converters +package dev.minios.pdaiv1.storage.converters import androidx.room.TypeConverter import java.util.* diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/converters/ListConverters.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/converters/ListConverters.kt similarity index 89% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/converters/ListConverters.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/converters/ListConverters.kt index a8751785a..e83869d4c 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/converters/ListConverters.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/converters/ListConverters.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.converters +package dev.minios.pdaiv1.storage.converters import androidx.room.TypeConverter import com.google.gson.Gson diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/converters/MapConverters.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/converters/MapConverters.kt similarity index 89% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/converters/MapConverters.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/converters/MapConverters.kt index b21d9bbae..f5f285711 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/converters/MapConverters.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/converters/MapConverters.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.converters +package dev.minios.pdaiv1.storage.converters import androidx.room.TypeConverter import com.google.gson.Gson diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/CacheDatabase.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/CacheDatabase.kt new file mode 100755 index 000000000..c6b39b18b --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/CacheDatabase.kt @@ -0,0 +1,53 @@ +package dev.minios.pdaiv1.storage.db.cache + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import dev.minios.pdaiv1.storage.converters.ListConverters +import dev.minios.pdaiv1.storage.converters.MapConverters +import dev.minios.pdaiv1.storage.db.cache.CacheDatabase.Companion.DB_VERSION +import dev.minios.pdaiv1.storage.db.cache.dao.ServerConfigurationDao +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionEmbeddingDao +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionHyperNetworkDao +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionLoraDao +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionModelDao +import dev.minios.pdaiv1.storage.db.cache.dao.StableDiffusionSamplerDao +import dev.minios.pdaiv1.storage.db.cache.dao.SwarmUiModelDao +import dev.minios.pdaiv1.storage.db.cache.entity.ServerConfigurationEntity +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionLoraEntity +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionModelEntity +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionSamplerEntity +import dev.minios.pdaiv1.storage.db.cache.entity.SwarmUiModelEntity + +@Database( + version = DB_VERSION, + exportSchema = true, + entities = [ + ServerConfigurationEntity::class, + StableDiffusionModelEntity::class, + StableDiffusionSamplerEntity::class, + StableDiffusionLoraEntity::class, + StableDiffusionHyperNetworkEntity::class, + StableDiffusionEmbeddingEntity::class, + SwarmUiModelEntity::class, + ], +) +@TypeConverters( + MapConverters::class, + ListConverters::class, +) +internal abstract class CacheDatabase : RoomDatabase() { + abstract fun serverConfigurationDao(): ServerConfigurationDao + abstract fun sdModelDao(): StableDiffusionModelDao + abstract fun sdSamplerDao(): StableDiffusionSamplerDao + abstract fun sdLoraDao(): StableDiffusionLoraDao + abstract fun sdHyperNetworkDao(): StableDiffusionHyperNetworkDao + abstract fun sdEmbeddingDao(): StableDiffusionEmbeddingDao + abstract fun swarmUiModelDao(): SwarmUiModelDao + + companion object { + const val DB_VERSION = 1 + } +} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/ServerConfigurationContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/ServerConfigurationContract.kt similarity index 76% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/ServerConfigurationContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/ServerConfigurationContract.kt index fa098eda3..7e771cd5c 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/ServerConfigurationContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/ServerConfigurationContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.cache.contract +package dev.minios.pdaiv1.storage.db.cache.contract internal object ServerConfigurationContract { const val TABLE = "server_config" diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionEmbeddingContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionEmbeddingContract.kt new file mode 100644 index 000000000..691f1f10b --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionEmbeddingContract.kt @@ -0,0 +1,8 @@ +package dev.minios.pdaiv1.storage.db.cache.contract + +object StableDiffusionEmbeddingContract { + const val TABLE = "embeddings" + + const val ID = "id" + const val KEYWORD = "keyword" +} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionHyperNetworkContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionHyperNetworkContract.kt similarity index 75% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionHyperNetworkContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionHyperNetworkContract.kt index 417761ab3..a4575937b 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionHyperNetworkContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionHyperNetworkContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.cache.contract +package dev.minios.pdaiv1.storage.db.cache.contract object StableDiffusionHyperNetworkContract { const val TABLE = "hyper_networks" diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionLoraContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionLoraContract.kt similarity index 76% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionLoraContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionLoraContract.kt index e15120c12..ff2504dac 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionLoraContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionLoraContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.cache.contract +package dev.minios.pdaiv1.storage.db.cache.contract object StableDiffusionLoraContract { const val TABLE = "loras" diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionModelContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionModelContract.kt similarity index 84% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionModelContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionModelContract.kt index a2db1fa9e..6ba217f0d 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionModelContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionModelContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.cache.contract +package dev.minios.pdaiv1.storage.db.cache.contract internal object StableDiffusionModelContract { const val TABLE = "sd_models" diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionSamplerContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionSamplerContract.kt similarity index 78% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionSamplerContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionSamplerContract.kt index 9f68a22dc..3a479d0c3 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/StableDiffusionSamplerContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/StableDiffusionSamplerContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.cache.contract +package dev.minios.pdaiv1.storage.db.cache.contract internal object StableDiffusionSamplerContract { const val TABLE = "sd_samplers" diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/SwarmUiModelContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/SwarmUiModelContract.kt similarity index 77% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/SwarmUiModelContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/SwarmUiModelContract.kt index 2a900c591..f03cd81f8 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/contract/SwarmUiModelContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/contract/SwarmUiModelContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.cache.contract +package dev.minios.pdaiv1.storage.db.cache.contract internal object SwarmUiModelContract { const val TABLE = "swarm_models" diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/ServerConfigurationDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/ServerConfigurationDao.kt new file mode 100755 index 000000000..54ed6203b --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/ServerConfigurationDao.kt @@ -0,0 +1,20 @@ +package dev.minios.pdaiv1.storage.db.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.cache.contract.ServerConfigurationContract +import dev.minios.pdaiv1.storage.db.cache.entity.ServerConfigurationEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface ServerConfigurationDao { + + @Query("SELECT * FROM ${ServerConfigurationContract.TABLE} LIMIT 1") + fun query(): Single + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(item: ServerConfigurationEntity): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionEmbeddingDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionEmbeddingDao.kt new file mode 100644 index 000000000..da69e261c --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionEmbeddingDao.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.storage.db.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionEmbeddingContract +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionEmbeddingEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface StableDiffusionEmbeddingDao { + + @Query("SELECT * FROM ${StableDiffusionEmbeddingContract.TABLE}") + fun queryAll(): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertList(items: List): Completable + + @Query("DELETE FROM ${StableDiffusionEmbeddingContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionHyperNetworkDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionHyperNetworkDao.kt new file mode 100644 index 000000000..f770e77db --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionHyperNetworkDao.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.storage.db.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionHyperNetworkContract +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionHyperNetworkEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface StableDiffusionHyperNetworkDao { + + @Query("SELECT * FROM ${StableDiffusionHyperNetworkContract.TABLE}") + fun queryAll(): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertList(items: List): Completable + + @Query("DELETE FROM ${StableDiffusionHyperNetworkContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionLoraDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionLoraDao.kt new file mode 100644 index 000000000..475852e4e --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionLoraDao.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.storage.db.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionLoraContract +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionLoraEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface StableDiffusionLoraDao { + + @Query("SELECT * FROM ${StableDiffusionLoraContract.TABLE}") + fun queryAll(): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertList(items: List): Completable + + @Query("DELETE FROM ${StableDiffusionLoraContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionModelDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionModelDao.kt new file mode 100755 index 000000000..061a358ed --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionModelDao.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.storage.db.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionModelContract +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionModelEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface StableDiffusionModelDao { + + @Query("SELECT * FROM ${StableDiffusionModelContract.TABLE}") + fun queryAll(): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertList(items: List): Completable + + @Query("DELETE FROM ${StableDiffusionModelContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionSamplerDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionSamplerDao.kt new file mode 100755 index 000000000..c0ebe624f --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/StableDiffusionSamplerDao.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.storage.db.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionSamplerContract +import dev.minios.pdaiv1.storage.db.cache.entity.StableDiffusionSamplerEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface StableDiffusionSamplerDao { + + @Query("SELECT * FROM ${StableDiffusionSamplerContract.TABLE}") + fun queryAll(): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertList(items: List): Completable + + @Query("DELETE FROM ${StableDiffusionSamplerContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/SwarmUiModelDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/SwarmUiModelDao.kt new file mode 100644 index 000000000..012c32997 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/dao/SwarmUiModelDao.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.storage.db.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.cache.contract.SwarmUiModelContract +import dev.minios.pdaiv1.storage.db.cache.entity.SwarmUiModelEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface SwarmUiModelDao { + + @Query("SELECT * FROM ${SwarmUiModelContract.TABLE}") + fun queryAll(): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertList(items: List): Completable + + @Query("DELETE FROM ${SwarmUiModelContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/ServerConfigurationEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/ServerConfigurationEntity.kt similarity index 75% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/ServerConfigurationEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/ServerConfigurationEntity.kt index ce72c816f..f80829c12 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/ServerConfigurationEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/ServerConfigurationEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.cache.entity +package dev.minios.pdaiv1.storage.db.cache.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.cache.contract.ServerConfigurationContract +import dev.minios.pdaiv1.storage.db.cache.contract.ServerConfigurationContract @Entity(tableName = ServerConfigurationContract.TABLE) data class ServerConfigurationEntity( diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionEmbeddingEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionEmbeddingEntity.kt new file mode 100644 index 000000000..a91ac5e63 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionEmbeddingEntity.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.storage.db.cache.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionEmbeddingContract + +@Entity(tableName = StableDiffusionEmbeddingContract.TABLE) +data class StableDiffusionEmbeddingEntity( + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = StableDiffusionEmbeddingContract.ID) + val id: String, + @ColumnInfo(name = StableDiffusionEmbeddingContract.KEYWORD) + val keyword: String, +) diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionHyperNetworkEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionHyperNetworkEntity.kt similarity index 77% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionHyperNetworkEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionHyperNetworkEntity.kt index a78d82aa5..c65824d28 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionHyperNetworkEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionHyperNetworkEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.cache.entity +package dev.minios.pdaiv1.storage.db.cache.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionHyperNetworkContract +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionHyperNetworkContract @Entity(tableName = StableDiffusionHyperNetworkContract.TABLE) data class StableDiffusionHyperNetworkEntity( diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionLoraEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionLoraEntity.kt similarity index 79% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionLoraEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionLoraEntity.kt index 9b7f63f15..674096bc2 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionLoraEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionLoraEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.cache.entity +package dev.minios.pdaiv1.storage.db.cache.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionLoraContract +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionLoraContract @Entity(tableName = StableDiffusionLoraContract.TABLE) data class StableDiffusionLoraEntity( diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionModelEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionModelEntity.kt similarity index 85% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionModelEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionModelEntity.kt index b185f07cc..04ec574a4 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionModelEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionModelEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.cache.entity +package dev.minios.pdaiv1.storage.db.cache.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionModelContract +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionModelContract @Entity(tableName = StableDiffusionModelContract.TABLE) data class StableDiffusionModelEntity( diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionSamplerEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionSamplerEntity.kt similarity index 80% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionSamplerEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionSamplerEntity.kt index 14a54c2d7..323d3502d 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/StableDiffusionSamplerEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/StableDiffusionSamplerEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.cache.entity +package dev.minios.pdaiv1.storage.db.cache.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.cache.contract.StableDiffusionSamplerContract +import dev.minios.pdaiv1.storage.db.cache.contract.StableDiffusionSamplerContract @Entity(tableName = StableDiffusionSamplerContract.TABLE) data class StableDiffusionSamplerEntity( diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/SwarmUiModelEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/SwarmUiModelEntity.kt similarity index 79% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/SwarmUiModelEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/SwarmUiModelEntity.kt index f79f12b0f..cabc9a521 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/cache/entity/SwarmUiModelEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/cache/entity/SwarmUiModelEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.cache.entity +package dev.minios.pdaiv1.storage.db.cache.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.cache.contract.SwarmUiModelContract +import dev.minios.pdaiv1.storage.db.cache.contract.SwarmUiModelContract @Entity(tableName = SwarmUiModelContract.TABLE) data class SwarmUiModelEntity( diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/PersistentDatabase.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/PersistentDatabase.kt new file mode 100644 index 000000000..f826e3808 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/PersistentDatabase.kt @@ -0,0 +1,100 @@ +package dev.minios.pdaiv1.storage.db.persistent + +import androidx.room.AutoMigration +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import dev.minios.pdaiv1.storage.converters.* +import dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase.Companion.DB_VERSION +import dev.minios.pdaiv1.storage.db.persistent.contract.* +import dev.minios.pdaiv1.storage.db.persistent.dao.* +import dev.minios.pdaiv1.storage.db.persistent.entity.* + +@Database( + version = DB_VERSION, + exportSchema = true, + entities = [ + GenerationResultEntity::class, + LocalModelEntity::class, + HuggingFaceModelEntity::class, + SupporterEntity::class, + FalAiEndpointEntity::class, + ], + autoMigrations = [ + /** + * Added 3 fields to [GenerationResultEntity]: + * - [GenerationResultContract.SUB_SEED] + * - [GenerationResultContract.SUB_SEED_STRENGTH] + * - [GenerationResultContract.DENOISING_STRENGTH] + */ + AutoMigration(from = 1, to = 2), + /** + * Added [LocalModelEntity]. + */ + AutoMigration(from = 2, to = 3), + /** + * Added [HuggingFaceModelEntity]. + */ + AutoMigration(from = 3, to = 4), + /** + * Added [SupporterEntity]. + */ + AutoMigration(from = 4, to = 5), + /** + * Added 1 field to [LocalModelEntity]: + * - [LocalModelContract.TYPE] + */ + AutoMigration(from = 5, to = 6), + /** + * Added 1 field to [GenerationResultEntity]: + * - [GenerationResultContract.HIDDEN] + */ + AutoMigration(from = 6, to = 7), + /** + * Added [FalAiEndpointEntity]. + * Added 1 field to [FalAiEndpointEntity]: GROUP + * Added 3 fields to [GenerationResultEntity] for file-based media storage: + * - [GenerationResultContract.MEDIA_PATH] + * - [GenerationResultContract.INPUT_MEDIA_PATH] + * - [GenerationResultContract.MEDIA_TYPE] + */ + AutoMigration(from = 7, to = 8), + /** + * Added 1 field to [GenerationResultEntity]: + * - [GenerationResultContract.MODEL_NAME] + */ + AutoMigration(from = 8, to = 9), + /** + * Added 2 fields to [LocalModelEntity] for chipset filtering: + * - [LocalModelContract.CHIPSET_SUFFIX] + * - [LocalModelContract.RUN_ON_CPU] + */ + AutoMigration(from = 9, to = 10), + /** + * Added 1 field to [GenerationResultEntity]: + * - [GenerationResultContract.BLUR_HASH] + */ + AutoMigration(from = 10, to = 11), + /** + * Added 1 field to [GenerationResultEntity]: + * - [GenerationResultContract.LIKED] + */ + AutoMigration(from = 11, to = 12), + ], +) +@TypeConverters( + DateConverters::class, + ListConverters::class, +) +internal abstract class PersistentDatabase : RoomDatabase() { + abstract fun generationResultDao(): GenerationResultDao + abstract fun localModelDao(): LocalModelDao + abstract fun huggingFaceModelDao(): HuggingFaceModelDao + abstract fun supporterDao(): SupporterDao + abstract fun falAiEndpointDao(): FalAiEndpointDao + + companion object { + const val DB_NAME = "ai_sd_v1_storage_db" + const val DB_VERSION = 12 + } +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/FalAiEndpointContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/FalAiEndpointContract.kt new file mode 100644 index 000000000..d668ec206 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/FalAiEndpointContract.kt @@ -0,0 +1,16 @@ +package dev.minios.pdaiv1.storage.db.persistent.contract + +internal object FalAiEndpointContract { + const val TABLE = "fal_ai_endpoints" + const val ID = "id" + const val ENDPOINT_ID = "endpoint_id" + const val TITLE = "title" + const val DESCRIPTION = "description" + const val CATEGORY = "category" + const val GROUP = "group_name" + const val THUMBNAIL_URL = "thumbnail_url" + const val PLAYGROUND_URL = "playground_url" + const val DOCUMENTATION_URL = "documentation_url" + const val IS_CUSTOM = "is_custom" + const val SCHEMA_JSON = "schema_json" +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/GenerationResultContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/GenerationResultContract.kt new file mode 100644 index 000000000..30310c821 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/GenerationResultContract.kt @@ -0,0 +1,31 @@ +package dev.minios.pdaiv1.storage.db.persistent.contract + +internal object GenerationResultContract { + const val TABLE = "generation_results" + + const val ID = "id" + const val IMAGE_BASE_64 = "image_base_64" + const val ORIGINAL_IMAGE_BASE_64 = "original_image_base_64" + const val CREATED_AT = "created_at" + const val GENERATION_TYPE = "generation_type" + + const val PROMPT = "prompt" + const val NEGATIVE_PROMPT = "negative_prompt" + const val WIDTH = "width" + const val HEIGHT = "height" + const val SAMPLING_STEPS = "sampling_steps" + const val CFG_SCALE = "cfg_scale" + const val RESTORE_FACES = "restore_faces" + const val SAMPLER = "sampler" + const val SEED = "seed" + const val SUB_SEED = "sub_seed" + const val SUB_SEED_STRENGTH = "sub_seed_strength" + const val DENOISING_STRENGTH = "denoising_strength" + const val HIDDEN = "hidden" + const val LIKED = "liked" + const val MEDIA_PATH = "media_path" + const val INPUT_MEDIA_PATH = "input_media_path" + const val MEDIA_TYPE = "media_type" + const val MODEL_NAME = "model_name" + const val BLUR_HASH = "blur_hash" +} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/HuggingFaceModelContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/HuggingFaceModelContract.kt similarity index 76% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/HuggingFaceModelContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/HuggingFaceModelContract.kt index 6623d3def..f827c1819 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/HuggingFaceModelContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/HuggingFaceModelContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.persistent.contract +package dev.minios.pdaiv1.storage.db.persistent.contract object HuggingFaceModelContract { const val TABLE = "hugging_face_models" diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/LocalModelContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/LocalModelContract.kt new file mode 100644 index 000000000..045b0d87a --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/LocalModelContract.kt @@ -0,0 +1,13 @@ +package dev.minios.pdaiv1.storage.db.persistent.contract + +object LocalModelContract { + const val TABLE = "local_models" + + const val ID = "id" + const val TYPE = "type" + const val NAME = "name" + const val SIZE = "size" + const val SOURCES = "sources" + const val CHIPSET_SUFFIX = "chipset_suffix" + const val RUN_ON_CPU = "run_on_cpu" +} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/SupporterContract.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/SupporterContract.kt similarity index 75% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/SupporterContract.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/SupporterContract.kt index a9bdb7301..dde5ef759 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/contract/SupporterContract.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/contract/SupporterContract.kt @@ -1,4 +1,4 @@ -package com.shifthackz.aisdv1.storage.db.persistent.contract +package dev.minios.pdaiv1.storage.db.persistent.contract internal object SupporterContract { const val TABLE = "supporters" diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/FalAiEndpointDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/FalAiEndpointDao.kt new file mode 100644 index 000000000..70545495d --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/FalAiEndpointDao.kt @@ -0,0 +1,33 @@ +package dev.minios.pdaiv1.storage.db.persistent.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.persistent.contract.FalAiEndpointContract +import dev.minios.pdaiv1.storage.db.persistent.entity.FalAiEndpointEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single + +@Dao +interface FalAiEndpointDao { + + @Query("SELECT * FROM ${FalAiEndpointContract.TABLE}") + fun observeAll(): Flowable> + + @Query("SELECT * FROM ${FalAiEndpointContract.TABLE}") + fun queryAll(): Single> + + @Query("SELECT * FROM ${FalAiEndpointContract.TABLE} WHERE ${FalAiEndpointContract.ID} = :id") + fun queryById(id: String): Single + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(entity: FalAiEndpointEntity): Completable + + @Query("DELETE FROM ${FalAiEndpointContract.TABLE} WHERE ${FalAiEndpointContract.ID} = :id") + fun deleteById(id: String): Completable + + @Query("DELETE FROM ${FalAiEndpointContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/GenerationResultDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/GenerationResultDao.kt new file mode 100644 index 000000000..e81e217b0 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/GenerationResultDao.kt @@ -0,0 +1,67 @@ +package dev.minios.pdaiv1.storage.db.persistent.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.persistent.contract.GenerationResultContract +import dev.minios.pdaiv1.storage.db.persistent.entity.GenerationResultEntity +import dev.minios.pdaiv1.storage.db.persistent.entity.IdWithBlurHash +import dev.minios.pdaiv1.storage.db.persistent.entity.ThumbnailInfo +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface GenerationResultDao { + + @Query("SELECT * FROM ${GenerationResultContract.TABLE} ORDER BY ${GenerationResultContract.CREATED_AT} DESC") + fun query(): Single> + + @Query("SELECT ${GenerationResultContract.ID} FROM ${GenerationResultContract.TABLE} ORDER BY ${GenerationResultContract.CREATED_AT} DESC") + fun queryAllIds(): Single> + + @Query("SELECT ${GenerationResultContract.ID}, ${GenerationResultContract.BLUR_HASH} FROM ${GenerationResultContract.TABLE} ORDER BY ${GenerationResultContract.CREATED_AT} DESC") + fun queryAllIdsWithBlurHash(): Single> + + @Query("SELECT ${GenerationResultContract.ID}, ${GenerationResultContract.MEDIA_PATH}, ${GenerationResultContract.HIDDEN}, ${GenerationResultContract.BLUR_HASH} FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} IN (:idList)") + fun queryThumbnailInfoByIdList(idList: List): Single> + + @Query("SELECT ${GenerationResultContract.ID} FROM ${GenerationResultContract.TABLE} ORDER BY ${GenerationResultContract.CREATED_AT} DESC LIMIT :limit OFFSET :offset") + fun queryPageIds(limit: Int, offset: Int): Single> + + @Query("SELECT * FROM ${GenerationResultContract.TABLE} ORDER BY ${GenerationResultContract.CREATED_AT} DESC LIMIT :limit OFFSET :offset ") + fun queryPage(limit: Int, offset: Int): Single> + + @Query("SELECT * FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} = :id LIMIT 1") + fun queryById(id: Long): Single + + @Query("SELECT * FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} IN (:idList)") + fun queryByIdList(idList: List): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(item: GenerationResultEntity): Single + + @Query("DELETE FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} = :id") + fun deleteById(id: Long): Completable + + @Query("DELETE FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.ID} IN (:idList)") + fun deleteByIdList(idList: List): Completable + + @Query("DELETE FROM ${GenerationResultContract.TABLE}") + fun deleteAll(): Completable + + @Query("DELETE FROM ${GenerationResultContract.TABLE} WHERE ${GenerationResultContract.LIKED} = 0") + fun deleteAllUnliked(): Completable + + @Query("UPDATE ${GenerationResultContract.TABLE} SET ${GenerationResultContract.LIKED} = 1 WHERE ${GenerationResultContract.ID} IN (:idList)") + fun likeByIds(idList: List): Completable + + @Query("UPDATE ${GenerationResultContract.TABLE} SET ${GenerationResultContract.LIKED} = 0 WHERE ${GenerationResultContract.ID} IN (:idList)") + fun unlikeByIds(idList: List): Completable + + @Query("UPDATE ${GenerationResultContract.TABLE} SET ${GenerationResultContract.HIDDEN} = 1 WHERE ${GenerationResultContract.ID} IN (:idList)") + fun hideByIds(idList: List): Completable + + @Query("UPDATE ${GenerationResultContract.TABLE} SET ${GenerationResultContract.HIDDEN} = 0 WHERE ${GenerationResultContract.ID} IN (:idList)") + fun unhideByIds(idList: List): Completable +} diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/HuggingFaceModelDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/HuggingFaceModelDao.kt similarity index 79% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/HuggingFaceModelDao.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/HuggingFaceModelDao.kt index f93e87531..2909068ae 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/HuggingFaceModelDao.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/HuggingFaceModelDao.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.storage.db.persistent.dao +package dev.minios.pdaiv1.storage.db.persistent.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.persistent.contract.HuggingFaceModelContract -import com.shifthackz.aisdv1.storage.db.persistent.entity.HuggingFaceModelEntity +import dev.minios.pdaiv1.storage.db.persistent.contract.HuggingFaceModelContract +import dev.minios.pdaiv1.storage.db.persistent.entity.HuggingFaceModelEntity import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/LocalModelDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/LocalModelDao.kt similarity index 85% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/LocalModelDao.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/LocalModelDao.kt index a2b94a16b..a76e82022 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/dao/LocalModelDao.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/LocalModelDao.kt @@ -1,11 +1,11 @@ -package com.shifthackz.aisdv1.storage.db.persistent.dao +package dev.minios.pdaiv1.storage.db.persistent.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import com.shifthackz.aisdv1.storage.db.persistent.contract.LocalModelContract -import com.shifthackz.aisdv1.storage.db.persistent.entity.LocalModelEntity +import dev.minios.pdaiv1.storage.db.persistent.contract.LocalModelContract +import dev.minios.pdaiv1.storage.db.persistent.entity.LocalModelEntity import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/SupporterDao.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/SupporterDao.kt new file mode 100644 index 000000000..25337af5b --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/dao/SupporterDao.kt @@ -0,0 +1,23 @@ +package dev.minios.pdaiv1.storage.db.persistent.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.minios.pdaiv1.storage.db.persistent.contract.SupporterContract +import dev.minios.pdaiv1.storage.db.persistent.entity.SupporterEntity +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single + +@Dao +interface SupporterDao { + + @Query("SELECT * FROM ${SupporterContract.TABLE} ORDER BY ${SupporterContract.DATE} DESC") + fun queryAll(): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertList(items: List): Completable + + @Query("DELETE FROM ${SupporterContract.TABLE}") + fun deleteAll(): Completable +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/FalAiEndpointEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/FalAiEndpointEntity.kt new file mode 100644 index 000000000..4829ee9d9 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/FalAiEndpointEntity.kt @@ -0,0 +1,43 @@ +package dev.minios.pdaiv1.storage.db.persistent.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import dev.minios.pdaiv1.storage.db.persistent.contract.FalAiEndpointContract + +@Entity(tableName = FalAiEndpointContract.TABLE) +data class FalAiEndpointEntity( + @PrimaryKey + @ColumnInfo(name = FalAiEndpointContract.ID) + val id: String, + + @ColumnInfo(name = FalAiEndpointContract.ENDPOINT_ID) + val endpointId: String, + + @ColumnInfo(name = FalAiEndpointContract.TITLE) + val title: String, + + @ColumnInfo(name = FalAiEndpointContract.DESCRIPTION) + val description: String, + + @ColumnInfo(name = FalAiEndpointContract.CATEGORY) + val category: String, + + @ColumnInfo(name = FalAiEndpointContract.GROUP, defaultValue = "Custom") + val group: String, + + @ColumnInfo(name = FalAiEndpointContract.THUMBNAIL_URL) + val thumbnailUrl: String, + + @ColumnInfo(name = FalAiEndpointContract.PLAYGROUND_URL) + val playgroundUrl: String, + + @ColumnInfo(name = FalAiEndpointContract.DOCUMENTATION_URL) + val documentationUrl: String, + + @ColumnInfo(name = FalAiEndpointContract.IS_CUSTOM, defaultValue = "1") + val isCustom: Boolean, + + @ColumnInfo(name = FalAiEndpointContract.SCHEMA_JSON) + val schemaJson: String, +) diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/GenerationResultEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/GenerationResultEntity.kt new file mode 100644 index 000000000..21ef56cd1 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/GenerationResultEntity.kt @@ -0,0 +1,60 @@ +package dev.minios.pdaiv1.storage.db.persistent.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import dev.minios.pdaiv1.storage.db.persistent.contract.GenerationResultContract +import java.util.Date + +@Entity(tableName = GenerationResultContract.TABLE) +data class GenerationResultEntity( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = GenerationResultContract.ID) + val id: Long, + @ColumnInfo(name = GenerationResultContract.IMAGE_BASE_64) + val imageBase64: String, + @ColumnInfo(name = GenerationResultContract.ORIGINAL_IMAGE_BASE_64) + val originalImageBase64: String, + @ColumnInfo(name = GenerationResultContract.CREATED_AT) + val createdAt: Date, + @ColumnInfo(name = GenerationResultContract.GENERATION_TYPE) + val generationType: String, + @ColumnInfo(name = GenerationResultContract.PROMPT) + val prompt: String, + @ColumnInfo(name = GenerationResultContract.NEGATIVE_PROMPT) + val negativePrompt: String, + @ColumnInfo(name = GenerationResultContract.WIDTH) + val width: Int, + @ColumnInfo(name = GenerationResultContract.HEIGHT) + val height: Int, + @ColumnInfo(name = GenerationResultContract.SAMPLING_STEPS) + val samplingSteps: Int, + @ColumnInfo(name = GenerationResultContract.CFG_SCALE) + val cfgScale: Float, + @ColumnInfo(name = GenerationResultContract.RESTORE_FACES) + val restoreFaces: Boolean, + @ColumnInfo(name = GenerationResultContract.SAMPLER) + val sampler: String, + @ColumnInfo(name = GenerationResultContract.SEED) + val seed: String, + @ColumnInfo(name = GenerationResultContract.SUB_SEED, defaultValue = "") + val subSeed: String, + @ColumnInfo(name = GenerationResultContract.SUB_SEED_STRENGTH, defaultValue = "${0f}") + val subSeedStrength: Float, + @ColumnInfo(name = GenerationResultContract.DENOISING_STRENGTH, defaultValue = "${0f}") + val denoisingStrength: Float, + @ColumnInfo(name = GenerationResultContract.HIDDEN, defaultValue = "0") + val hidden: Boolean, + @ColumnInfo(name = GenerationResultContract.LIKED, defaultValue = "0") + val liked: Boolean, + @ColumnInfo(name = GenerationResultContract.MEDIA_PATH, defaultValue = "") + val mediaPath: String, + @ColumnInfo(name = GenerationResultContract.INPUT_MEDIA_PATH, defaultValue = "") + val inputMediaPath: String, + @ColumnInfo(name = GenerationResultContract.MEDIA_TYPE, defaultValue = "IMAGE") + val mediaType: String, + @ColumnInfo(name = GenerationResultContract.MODEL_NAME, defaultValue = "") + val modelName: String, + @ColumnInfo(name = GenerationResultContract.BLUR_HASH, defaultValue = "") + val blurHash: String, +) diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/HuggingFaceModelEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/HuggingFaceModelEntity.kt similarity index 78% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/HuggingFaceModelEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/HuggingFaceModelEntity.kt index dc5672abc..6215df01a 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/HuggingFaceModelEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/HuggingFaceModelEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.persistent.entity +package dev.minios.pdaiv1.storage.db.persistent.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.persistent.contract.HuggingFaceModelContract +import dev.minios.pdaiv1.storage.db.persistent.contract.HuggingFaceModelContract @Entity(tableName = HuggingFaceModelContract.TABLE) data class HuggingFaceModelEntity( diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/IdWithBlurHash.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/IdWithBlurHash.kt new file mode 100644 index 000000000..d04c55eb1 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/IdWithBlurHash.kt @@ -0,0 +1,15 @@ +package dev.minios.pdaiv1.storage.db.persistent.entity + +import androidx.room.ColumnInfo +import dev.minios.pdaiv1.storage.db.persistent.contract.GenerationResultContract + +/** + * Lightweight projection for gallery listing. + * Contains only ID and BlurHash for efficient placeholder rendering. + */ +data class IdWithBlurHash( + @ColumnInfo(name = GenerationResultContract.ID) + val id: Long, + @ColumnInfo(name = GenerationResultContract.BLUR_HASH) + val blurHash: String, +) diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/LocalModelEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/LocalModelEntity.kt new file mode 100644 index 000000000..f7c5d01f3 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/LocalModelEntity.kt @@ -0,0 +1,25 @@ +package dev.minios.pdaiv1.storage.db.persistent.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import dev.minios.pdaiv1.storage.db.persistent.contract.LocalModelContract + +@Entity(tableName = LocalModelContract.TABLE) +data class LocalModelEntity( + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = LocalModelContract.ID) + val id: String, + @ColumnInfo(name = LocalModelContract.TYPE, defaultValue = "onnx") + val type: String, + @ColumnInfo(name = LocalModelContract.NAME) + val name: String, + @ColumnInfo(name = LocalModelContract.SIZE) + val size: String, + @ColumnInfo(name = LocalModelContract.SOURCES) + val sources: List, + @ColumnInfo(name = LocalModelContract.CHIPSET_SUFFIX, defaultValue = "NULL") + val chipsetSuffix: String? = null, + @ColumnInfo(name = LocalModelContract.RUN_ON_CPU, defaultValue = "0") + val runOnCpu: Boolean = false, +) diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/SupporterEntity.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/SupporterEntity.kt similarity index 78% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/SupporterEntity.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/SupporterEntity.kt index 81e56da43..8339e7e3e 100644 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/db/persistent/entity/SupporterEntity.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/SupporterEntity.kt @@ -1,9 +1,9 @@ -package com.shifthackz.aisdv1.storage.db.persistent.entity +package dev.minios.pdaiv1.storage.db.persistent.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.shifthackz.aisdv1.storage.db.persistent.contract.SupporterContract +import dev.minios.pdaiv1.storage.db.persistent.contract.SupporterContract import java.util.Date @Entity(tableName = SupporterContract.TABLE) diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/ThumbnailInfo.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/ThumbnailInfo.kt new file mode 100644 index 000000000..8eecd0678 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/db/persistent/entity/ThumbnailInfo.kt @@ -0,0 +1,19 @@ +package dev.minios.pdaiv1.storage.db.persistent.entity + +import androidx.room.ColumnInfo +import dev.minios.pdaiv1.storage.db.persistent.contract.GenerationResultContract + +/** + * Lightweight projection for thumbnail loading. + * Contains only necessary fields to load thumbnail from file. + */ +data class ThumbnailInfo( + @ColumnInfo(name = GenerationResultContract.ID) + val id: Long, + @ColumnInfo(name = GenerationResultContract.MEDIA_PATH) + val mediaPath: String, + @ColumnInfo(name = GenerationResultContract.HIDDEN) + val hidden: Boolean, + @ColumnInfo(name = GenerationResultContract.BLUR_HASH) + val blurHash: String, +) diff --git a/storage/src/main/java/com/shifthackz/aisdv1/storage/di/DatabaseModule.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/di/DatabaseModule.kt similarity index 81% rename from storage/src/main/java/com/shifthackz/aisdv1/storage/di/DatabaseModule.kt rename to storage/src/main/java/dev/minios/pdaiv1/storage/di/DatabaseModule.kt index 5c22374a4..dbec9ad9d 100755 --- a/storage/src/main/java/com/shifthackz/aisdv1/storage/di/DatabaseModule.kt +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/di/DatabaseModule.kt @@ -1,10 +1,10 @@ -package com.shifthackz.aisdv1.storage.di +package dev.minios.pdaiv1.storage.di import androidx.room.Room -import com.shifthackz.aisdv1.storage.db.cache.CacheDatabase -import com.shifthackz.aisdv1.storage.db.persistent.PersistentDatabase -import com.shifthackz.aisdv1.storage.gateway.GatewayClearCacheDb -import com.shifthackz.aisdv1.storage.gateway.GatewayClearPersistentDb +import dev.minios.pdaiv1.storage.db.cache.CacheDatabase +import dev.minios.pdaiv1.storage.db.persistent.PersistentDatabase +import dev.minios.pdaiv1.storage.gateway.GatewayClearCacheDb +import dev.minios.pdaiv1.storage.gateway.GatewayClearPersistentDb import org.koin.android.ext.koin.androidApplication import org.koin.dsl.module @@ -51,5 +51,6 @@ val databaseModule = module { single { get().localModelDao() } single { get().huggingFaceModelDao() } single { get().supporterDao() } + single { get().falAiEndpointDao() } //endregion } diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/gateway/GatewayClearCacheDb.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/gateway/GatewayClearCacheDb.kt new file mode 100644 index 000000000..66b5df581 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/gateway/GatewayClearCacheDb.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.storage.gateway + +fun interface GatewayClearCacheDb { + operator fun invoke() +} diff --git a/storage/src/main/java/dev/minios/pdaiv1/storage/gateway/GatewayClearPersistentDb.kt b/storage/src/main/java/dev/minios/pdaiv1/storage/gateway/GatewayClearPersistentDb.kt new file mode 100644 index 000000000..721b282b8 --- /dev/null +++ b/storage/src/main/java/dev/minios/pdaiv1/storage/gateway/GatewayClearPersistentDb.kt @@ -0,0 +1,5 @@ +package dev.minios.pdaiv1.storage.gateway + +fun interface GatewayClearPersistentDb { + operator fun invoke() +}