diff --git a/.gitignore b/.gitignore index aa724b7..b73a58d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,14 @@ .externalNativeBuild .cxx local.properties + +# Kotlin incremental compilation cache +.kotlin/ + +# Module-level build output (root /build above only covers the top-level dir) +**/build/ + +# GPG keyring files — never commit signing keys +*.gpg +*.kbx +secring.gpg diff --git a/CHANGELOG.md b/CHANGELOG.md index aa5c8e0..288ee57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ # Changelog +## Unreleased + +### imagepicker +- Fluent builder API: `ImagePicker.with(context, caller).source().crop().onResult().onError().launch()` +- Gallery picking via `ActivityResultContracts.GetContent` +- Camera capture via `ActivityResultContracts.TakePicture` with automatic EXIF orientation correction +- `MediaSource.Gallery`, `MediaSource.Camera`, `MediaSource.Both` (Both falls back to Gallery) +- Sealed `ImagePickerResult`: `Success`, `SuccessWithBitmap`, `Cancelled`, `Error` +- Sealed `ImagePickerException`: `PermissionDenied`, `AppNotFound`, `FileCreationFailed`, `InvalidUri`, `DecodingFailed`, `FileDeletionFailed`, `IntentFailed`, `Unknown` +- Automatic CAMERA permission request before camera launch +- `CropImageLauncher` — chains gallery/camera result into `CropperActivity` when `crop(true)` +- `ImagePickerFileProvider` — isolated FileProvider subclass; authority `{applicationId}.imagepicker.provider` +- `` manifest entry for camera intent visibility on Android 11+ +- All `registerForActivityResult` calls happen at construction time (before `onStart`) + +### imagecropper +- `CropperView` — custom View with matrix-based image fit, touch-drag/resize crop rect +- `CropOverlayDrawer` — dimmed overlay, border, rule-of-thirds grid, L-shaped corner handles +- `CropTouchHandler` — detects MOVE / corner / edge areas; enforces `MIN_CROP_SIZE` +- `CropperSavedState` — full Parcelable save/restore of crop rect across rotation +- `CropperActivity` — standalone Activity: receives URI via `EXTRA_INPUT_URI`, returns cropped JPEG URI via `EXTRA_OUTPUT_URI` using `FileProvider` (`{applicationId}.imagecropper.provider`) +- `getCroppedImage()` — returns cropped `Bitmap` from current rect, clamped to image bounds + +### sample-app +- Compose UI: empty state → gallery pick → crop → result display +- Error and exception feedback via Toast + ## 0.1.0 -- Initial MediaKit OSS setup -- Added README -- Prepared repository structure for publishing +- Initial repository structure and OSS setup diff --git a/ImageCropper/build.gradle.kts b/ImageCropper/build.gradle.kts index 1b8e23d..31842db 100644 --- a/ImageCropper/build.gradle.kts +++ b/ImageCropper/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + `maven-publish` + signing } android { @@ -30,14 +32,82 @@ android { kotlinOptions { jvmTarget = "11" } + + publishing { + singleVariant("release") { + withSourcesJar() + withJavadocJar() + } + } } dependencies { - + api(project(":imagepicker")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) - implementation(libs.material) + implementation(libs.androidx.activity.ktx) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file +} + +afterEvaluate { + publishing { + publications { + create("release") { + from(components["release"]) + groupId = project.findProperty("GROUP_ID") as String + artifactId = "imagecropper" + version = project.findProperty("VERSION_NAME") as String + + pom { + name.set("MediaKit ImageCropper") + description.set("Touch-driven image cropping for Android. Custom CropperView with rule-of-thirds grid, corner handles, and state restoration. Integrates with MediaKit ImagePicker or standalone.") + url.set("https://github.com/AkshayAshokCode/MediaKit-android") + licenses { + license { + name.set("MIT License") + url.set("https://opensource.org/licenses/MIT") + } + } + developers { + developer { + id.set("akshayashokcode") + name.set("Akshay Ashok") + email.set("akshayashokan1054@gmail.com") + } + } + scm { + connection.set("scm:git:github.com/AkshayAshokCode/MediaKit-android.git") + developerConnection.set("scm:git:ssh://github.com/AkshayAshokCode/MediaKit-android.git") + url.set("https://github.com/AkshayAshokCode/MediaKit-android/tree/main") + } + } + } + } + + repositories { + maven { + name = "sonatype" + url = uri( + if ((project.findProperty("VERSION_NAME") as String).endsWith("SNAPSHOT")) + "https://s01.oss.sonatype.org/content/repositories/snapshots/" + else + "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + ) + credentials { + username = project.findProperty("OSSRH_USERNAME") as String? ?: "" + password = project.findProperty("OSSRH_PASSWORD") as String? ?: "" + } + } + } + } + + signing { + val signingKeyId = project.findProperty("SIGNING_KEY_ID") as String? + val signingKey = project.findProperty("SIGNING_KEY") as String? + val signingPassword = project.findProperty("SIGNING_PASSWORD") as String? + useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) + sign(publishing.publications["release"]) + } +} diff --git a/ImageCropper/consumer-rules.pro b/ImageCropper/consumer-rules.pro index e69de29..1c985ae 100644 --- a/ImageCropper/consumer-rules.pro +++ b/ImageCropper/consumer-rules.pro @@ -0,0 +1,8 @@ +# Keep CropperActivity — launched by intent from MediaKitCropProvider +-keep class com.akshayashokcode.imagecropper.CropperActivity { *; } + +# Keep MediaKitCropProvider so consumers can instantiate it by name or reflection +-keep class com.akshayashokcode.imagecropper.MediaKitCropProvider { *; } + +# Keep CropperView for consumers embedding it directly in XML layouts +-keep class com.akshayashokcode.imagecropper.CropperView { *; } diff --git a/ImageCropper/src/main/AndroidManifest.xml b/ImageCropper/src/main/AndroidManifest.xml index a5918e6..0f9e7ef 100644 --- a/ImageCropper/src/main/AndroidManifest.xml +++ b/ImageCropper/src/main/AndroidManifest.xml @@ -1,4 +1,18 @@ + + - \ No newline at end of file + + + + + diff --git a/ImageCropper/src/main/java/com/akshayashokcode/imagecropper/CropperActivity.kt b/ImageCropper/src/main/java/com/akshayashokcode/imagecropper/CropperActivity.kt new file mode 100644 index 0000000..eb3efdf --- /dev/null +++ b/ImageCropper/src/main/java/com/akshayashokcode/imagecropper/CropperActivity.kt @@ -0,0 +1,80 @@ +package com.akshayashokcode.imagecropper + +import android.content.Intent +import android.graphics.BitmapFactory +import android.os.Bundle +import android.widget.Button +import androidx.activity.ComponentActivity +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import java.io.File +import java.io.FileOutputStream + +class CropperActivity : ComponentActivity() { + + companion object { + const val EXTRA_INPUT_URI = "extra_input_uri" + const val EXTRA_OUTPUT_URI = "extra_output_uri" + } + + private lateinit var cropperView: CropperView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_cropper) + + cropperView = findViewById(R.id.cropperView) + + val cropButton = findViewById