diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ff1d74..2cc5f55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,11 +20,11 @@ jobs: - name: Checkout source uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Cache Gradle uses: actions/cache@v3 @@ -56,11 +56,11 @@ jobs: - name: Checkout source uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Cache Gradle uses: actions/cache@v3 @@ -105,11 +105,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Make gradlew executable run: chmod +x ./gradlew diff --git a/.gitignore b/.gitignore index f068e07..40bda1d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ .externalNativeBuild .cxx local.properties +/release-keystore.properties diff --git a/PrivacyPolicy.md b/PrivacyPolicy.md new file mode 100644 index 0000000..6fb16a7 --- /dev/null +++ b/PrivacyPolicy.md @@ -0,0 +1,68 @@ +**Privacy Policy** + +This privacy policy applies to the File Picker app (hereby referred to as "Application") for mobile devices that was created by Naresh Chocha (hereby referred to as "Service Provider") as an Open Source service. This service is intended for use "AS IS". + +**Information Collection and Use** + +The Application collects information when you download and use it. This information may include information such as + +* Your device's Internet Protocol address (e.g. IP address) +* The pages of the Application that you visit, the time and date of your visit, the time spent on those pages +* The time spent on the Application +* The operating system you use on your mobile device + +The Application does not gather precise information about the location of your mobile device. + +The Application collects your device's location, which helps the Service Provider determine your approximate geographical location and make use of in below ways: + +* Geolocation Services: The Service Provider utilizes location data to provide features such as personalized content, relevant recommendations, and location-based services. +* Analytics and Improvements: Aggregated and anonymized location data helps the Service Provider to analyze user behavior, identify trends, and improve the overall performance and functionality of the Application. +* Third-Party Services: Periodically, the Service Provider may transmit anonymized location data to external services. These services assist them in enhancing the Application and optimizing their offerings. + +The Service Provider may use the information you provided to contact you from time to time to provide you with important information, required notices and marketing promotions. + +For a better experience, while using the Application, the Service Provider may require you to provide us with certain personally identifiable information. The information that the Service Provider request will be retained by them and used as described in this privacy policy. + +**Third Party Access** + +Only aggregated, anonymized data is periodically transmitted to external services to aid the Service Provider in improving the Application and their service. The Service Provider may share your information with third parties in the ways that are described in this privacy statement. + +The Service Provider may disclose User Provided and Automatically Collected Information: + +* as required by law, such as to comply with a subpoena, or similar legal process; +* when they believe in good faith that disclosure is necessary to protect their rights, protect your safety or the safety of others, investigate fraud, or respond to a government request; +* with their trusted services providers who work on their behalf, do not have an independent use of the information we disclose to them, and have agreed to adhere to the rules set forth in this privacy statement. + +**Opt-Out Rights** + +You can stop all collection of information by the Application easily by uninstalling it. You may use the standard uninstall processes as may be available as part of your mobile device or via the mobile application marketplace or network. + +**Data Retention Policy** + +The Service Provider will retain User Provided data for as long as you use the Application and for a reasonable time thereafter. If you'd like them to delete User Provided Data that you have provided via the Application, please contact them at chochanaresh0@gmail.com and they will respond in a reasonable time. + +**Children** + +The Service Provider does not use the Application to knowingly solicit data from or market to children under the age of 13. + +The Service Provider does not knowingly collect personally identifiable information from children. The Service Provider encourages all children to never submit any personally identifiable information through the Application and/or Services. The Service Provider encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child has provided personally identifiable information to the Service Provider through the Application and/or Services, please contact the Service Provider (chochanaresh0@gmail.com) so that they will be able to take the necessary actions. You must also be at least 16 years of age to consent to the processing of your personally identifiable information in your country (in some countries we may allow your parent or guardian to do so on your behalf). + +**Security** + +The Service Provider is concerned about safeguarding the confidentiality of your information. The Service Provider provides physical, electronic, and procedural safeguards to protect information the Service Provider processes and maintains. + +**Changes** + +This Privacy Policy may be updated from time to time for any reason. The Service Provider will notify you of any changes to the Privacy Policy by updating this page with the new Privacy Policy. You are advised to consult this Privacy Policy regularly for any changes, as continued use is deemed approval of all changes. + +This privacy policy is effective as of 2025-06-24 + +**Your Consent** + +By using the Application, you are consenting to the processing of your information as set forth in this Privacy Policy now and as amended by us. + +**Contact Us** + +If you have any questions regarding privacy while using the Application, or have questions about the practices, please contact the Service Provider via email at chochanaresh0@gmail.com. + +* * * \ No newline at end of file diff --git a/README.md b/README.md index a4f6456..5d91f5f 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,47 @@ val pickerData = PickerData( ) ) ``` +# Migration Guide: FilePicker Library + +## Overview + +This guide outlines the changes from the old code to the new `FilePicker` implementation, focusing on the transition from the Builder pattern to `ActivityResultContract`. + +## Key Changes + +1. **Package and Class Changes**: + The package has changed from `com.nareshchocha.filepickerlibrary.ui` to `com.nareshchocha.filepickerlibrary`. + +2. **Removal of `Builder` Class**: + The `Builder` class is no longer needed. The new code utilizes `ActivityResultContract` for handling file picker actions. + +3. **Introduction of `ActivityResultContracts`**: + File picker operations are now handled by specific `ActivityResultContract` classes, such as `ImageCapture`, `VideoCapture`, and `PickMedia`. + +4. **Logging Support**: + A new `isLoggingEnabled` flag allows enabling logging in the contracts for debugging. + +## Migration Steps + +### 1. **Remove Builder Pattern** + +The `Builder` class is no longer needed. You should transition to using `ActivityResultContracts` instead. + +### 2. **Use `ActivityResultContracts`** + +You can now handle file picker actions with specific contracts. For example: + +- **Old Code**: + ```kotlin + fun imageCaptureBuild(mImageCaptureConfig: ImageCaptureConfig?): Intent = + ImageCaptureActivity.getInstance(context, mImageCaptureConfig) + ``` +- **New Code**: + ```kotlin + val imageCaptureResult = registerForActivityResult(FilePickerResultContracts.ImageCapture()) { result -> + // Handle result + } + ``` --- diff --git a/filepickerlibrary/build.gradle.kts b/filepickerlibrary/build.gradle.kts index f3a7972..a2564c5 100644 --- a/filepickerlibrary/build.gradle.kts +++ b/filepickerlibrary/build.gradle.kts @@ -1,4 +1,4 @@ -import com.vanniktech.maven.publish.SonatypeHost +import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) @@ -45,9 +45,10 @@ android { sourceCompatibility = JavaVersion.valueOf(libs.versions.jdkVersion.get()) targetCompatibility = JavaVersion.valueOf(libs.versions.jdkVersion.get()) } - - kotlinOptions { - jvmTarget = JavaVersion.valueOf(libs.versions.jdkVersion.get()).toString() + kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } } } @@ -61,40 +62,14 @@ dependencies { implementation(libs.androidx.material3) implementation(libs.androidx.material.icons.extended) - // testing - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) - testImplementation(libs.truth) - androidTestImplementation(libs.truth) - // testing compose androidTestImplementation(platform(libs.androidx.compose.bom)) - androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) - - // JUnit - testImplementation("junit:junit:4.13.2") - - // AndroidX Test - testImplementation("androidx.test:core:1.5.0") - testImplementation("androidx.test:runner:1.5.2") - testImplementation("androidx.test.ext:junit:1.1.5") - - // Robolectric for (Android framework simulation in unit tests) - testImplementation("org.robolectric:robolectric:4.10.3") - - // Mockito for mock(ing) - testImplementation("org.mockito:mockito-core:5.7.0") - testImplementation("org.mockito:mockito-inline:5.2.0") - // For mocking final classes) - // Optional: If you( need to mock Kotlin classes better) - testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1") } - +val automaticRelease: Boolean = true mavenPublishing { - publishToMavenCentral(SonatypeHost.S01, true) + publishToMavenCentral(automaticRelease) signAllPublications() coordinates("io.github.chochanaresh", "filepicker", versionName) diff --git a/filepickerlibrary/proguard-rules.pro b/filepickerlibrary/proguard-rules.pro index a36673f..a8994a8 100644 --- a/filepickerlibrary/proguard-rules.pro +++ b/filepickerlibrary/proguard-rules.pro @@ -1,34 +1 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle.kts. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -# -renamesourcefileattribute SourceFile --keepclasseswithmembernames class com.nareshchocha.filepickerlibrary.models.**{ - *; -} -#-keepclassmembers class * extends com.nareshchocha.filepickerlibrary.models.BaseConfig { - # *; - #} - #-keepclassmembers class * extends com.nareshchocha.filepickerlibrary.models.PickMediaType { - # *; - #} - --keepclassmembers class * extends androidx.appcompat.app.AppCompatActivity { - *; -} +-keep,allowobfuscation class com.nareshchocha.filepickerlibrary.** {*;} \ No newline at end of file diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/FilePickerResultContracts.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/FilePickerResultContracts.kt index e847b08..5e41b93 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/FilePickerResultContracts.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/FilePickerResultContracts.kt @@ -3,7 +3,6 @@ package com.nareshchocha.filepickerlibrary import android.app.Activity import android.content.Context import android.content.Intent -import android.os.Build import androidx.activity.result.contract.ActivityResultContract import com.nareshchocha.filepickerlibrary.models.BaseConfig import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig @@ -17,12 +16,8 @@ import com.nareshchocha.filepickerlibrary.ui.activitys.ImageCaptureActivity import com.nareshchocha.filepickerlibrary.ui.activitys.MediaFilePickerActivity import com.nareshchocha.filepickerlibrary.ui.activitys.PopUpActivity import com.nareshchocha.filepickerlibrary.ui.activitys.VideoCaptureActivity -import com.nareshchocha.filepickerlibrary.utilities.FileUtils import com.nareshchocha.filepickerlibrary.utilities.appConst.Const import com.nareshchocha.filepickerlibrary.utilities.getClipDataUris -import com.nareshchocha.filepickerlibrary.utilities.getDocumentFilePick -import com.nareshchocha.filepickerlibrary.utilities.getFilePathList -import com.nareshchocha.filepickerlibrary.utilities.getMediaIntent /** * A collection of ActivityResultContracts for different file picking operations. @@ -90,21 +85,10 @@ class FilePickerResultContracts private constructor() { * Returns a FilePickerResult that contains the URIs and file paths of the selected media. */ class PickMedia : ActivityResultContract() { - private var context: Context? = null - override fun createIntent( context: Context, input: PickMediaConfig? - ): Intent { - this.context = context - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - context.getMediaIntent( - input ?: PickMediaConfig() - ) - } else { - MediaFilePickerActivity.getInstance(context, input ?: PickMediaConfig()) - } - } + ): Intent = MediaFilePickerActivity.getInstance(context, input ?: PickMediaConfig()) override fun parseResult( resultCode: Int, @@ -114,23 +98,17 @@ class FilePickerResultContracts private constructor() { FilePickerResult(errorMessage = "Media selection failed or cancelled") } else { if (intent.clipData != null) { - val uris = intent.getClipDataUris() - val filePaths = uris.getFilePathList(context!!) - if (uris.isEmpty()) { - FilePickerResult(errorMessage = "No media selected") - } else { - FilePickerResult( - selectedFileUris = uris, - selectedFilePaths = filePaths - ) - } + FilePickerResult( + selectedFileUris = intent.getClipDataUris(), + selectedFilePaths = intent.getStringArrayListExtra(Const.BundleExtras.FILE_PATH_LIST) + ) } else if (intent.data != null) { FilePickerResult( selectedFileUri = intent.data, - selectedFilePath = intent.data?.let { FileUtils.getRealPath(context!!, it) } + selectedFilePath = intent.getStringExtra(Const.BundleExtras.FILE_PATH) ) } else { - FilePickerResult(errorMessage = "No media selected") + FilePickerResult(errorMessage = "No file selected") } } } @@ -140,19 +118,10 @@ class FilePickerResultContracts private constructor() { * Returns a FilePickerResult that contains the URIs and file paths of the selected documents. */ class PickDocumentFile : ActivityResultContract() { - private var context: Context? = null - override fun createIntent( context: Context, input: DocumentFilePickerConfig? - ): Intent { - this.context = context - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - getDocumentFilePick(input ?: DocumentFilePickerConfig()) - } else { - DocumentFilePickerActivity.getInstance(context, input ?: DocumentFilePickerConfig()) - } - } + ): Intent = DocumentFilePickerActivity.getInstance(context, input ?: DocumentFilePickerConfig()) override fun parseResult( resultCode: Int, @@ -162,23 +131,17 @@ class FilePickerResultContracts private constructor() { FilePickerResult(errorMessage = "Document selection failed or cancelled") } else { if (intent.clipData != null) { - val uris = intent.getClipDataUris() - val filePaths = uris.getFilePathList(context!!) - if (uris.isEmpty()) { - FilePickerResult(errorMessage = "No document selected") - } else { - FilePickerResult( - selectedFileUris = uris, - selectedFilePaths = filePaths - ) - } + FilePickerResult( + selectedFileUris = intent.getClipDataUris(), + selectedFilePaths = intent.getStringArrayListExtra(Const.BundleExtras.FILE_PATH_LIST) + ) } else if (intent.data != null) { FilePickerResult( selectedFileUri = intent.data, - selectedFilePath = intent.data?.let { FileUtils.getRealPath(context!!, it) } + selectedFilePath = intent.getStringExtra(Const.BundleExtras.FILE_PATH) ) } else { - FilePickerResult(errorMessage = "No document selected") + FilePickerResult(errorMessage = "No file selected") } } } @@ -266,6 +229,7 @@ class FilePickerResultContracts private constructor() { resultCode, intent ) + else -> FilePickerResult(errorMessage = "Unknown file type") } } diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/BaseConfig.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/BaseConfig.kt index 8112021..0552977 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/BaseConfig.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/BaseConfig.kt @@ -1,10 +1,8 @@ package com.nareshchocha.filepickerlibrary.models import android.os.Parcelable -import androidx.annotation.Keep import kotlinx.parcelize.Parcelize -@Keep() @Parcelize sealed class BaseConfig( open val popUpIcon: Int?, diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/PickerData.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/PickerData.kt index cdc0551..c5f5e16 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/PickerData.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/models/PickerData.kt @@ -4,21 +4,18 @@ import android.os.Build import android.os.Parcelable import android.provider.MediaStore import androidx.annotation.DrawableRes -import androidx.annotation.Keep import androidx.compose.runtime.Composable import com.nareshchocha.filepickerlibrary.R import com.nareshchocha.filepickerlibrary.utilities.appConst.Const import kotlinx.parcelize.Parcelize import java.io.File -@Keep @Parcelize data class PickerData( val mPopUpConfig: PopUpConfig? = null, val listIntents: List = emptyList() ) : Parcelable -@Keep @Parcelize data class PopUpConfig( val chooserTitle: String? = "Choose Option", @@ -40,7 +37,7 @@ enum class Orientation { @Parcelize data class ImageCaptureConfig( - @DrawableRes override val popUpIcon: Int? = R.drawable.ic_camera, + @param:DrawableRes override val popUpIcon: Int? = R.drawable.ic_camera, override val popUpText: String? = "Camera", val mFolder: File? = null, val fileName: String? = Const.DefaultPaths.defaultImageFile(), @@ -64,7 +61,7 @@ data class ImageCaptureConfig( @Parcelize data class VideoCaptureConfig( - @DrawableRes override val popUpIcon: Int? = R.drawable.ic_video, + @param:DrawableRes override val popUpIcon: Int? = R.drawable.ic_video, override val popUpText: String? = "Video", val mFolder: File? = null, val fileName: String? = Const.DefaultPaths.defaultVideoFile(), @@ -87,7 +84,7 @@ data class VideoCaptureConfig( @Parcelize data class PickMediaConfig( - @DrawableRes override val popUpIcon: Int? = R.drawable.ic_media, + @param:DrawableRes override val popUpIcon: Int? = R.drawable.ic_media, override val popUpText: String? = "Pick Media", val allowMultiple: Boolean? = false, /** @@ -124,7 +121,7 @@ data class PickMediaConfig( @Parcelize data class DocumentFilePickerConfig( - @DrawableRes override val popUpIcon: Int? = R.drawable.ic_file, + @param:DrawableRes override val popUpIcon: Int? = R.drawable.ic_file, override val popUpText: String? = "File Media", val allowMultiple: Boolean? = false, /** @@ -151,7 +148,6 @@ data class DocumentFilePickerConfig( ), Parcelable -@Keep @Parcelize enum class PopUpType : Parcelable { BOTTOM_SHEET, diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/DocumentFilePickerActivity.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/DocumentFilePickerActivity.kt index 5475d60..91c0941 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/DocumentFilePickerActivity.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/DocumentFilePickerActivity.kt @@ -2,10 +2,10 @@ package com.nareshchocha.filepickerlibrary.ui.activitys import android.content.Context import android.content.Intent -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -14,45 +14,60 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.core.os.BundleCompat import com.nareshchocha.filepickerlibrary.R import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig import com.nareshchocha.filepickerlibrary.ui.components.dialogs.AppRationaleDialog import com.nareshchocha.filepickerlibrary.ui.components.dialogs.AppSettingDialog +import com.nareshchocha.filepickerlibrary.utilities.FileUtils import com.nareshchocha.filepickerlibrary.utilities.PermissionLists import com.nareshchocha.filepickerlibrary.utilities.SinglePermissionManager import com.nareshchocha.filepickerlibrary.utilities.appConst.Const import com.nareshchocha.filepickerlibrary.utilities.extensions.asString import com.nareshchocha.filepickerlibrary.utilities.extensions.getActivityOrNull +import com.nareshchocha.filepickerlibrary.utilities.getClipDataUris import com.nareshchocha.filepickerlibrary.utilities.getDocumentFilePick -import com.nareshchocha.filepickerlibrary.utilities.setActivityResult +import com.nareshchocha.filepickerlibrary.utilities.getFilePathList import com.nareshchocha.filepickerlibrary.utilities.setCanceledResult +import com.nareshchocha.filepickerlibrary.utilities.setSuccessResult internal class DocumentFilePickerActivity : ComponentActivity() { private val mDocumentFilePickerConfig: DocumentFilePickerConfig? by lazy { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra( - Const.BundleInternalExtras.PICK_DOCUMENT, - DocumentFilePickerConfig::class.java - ) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(Const.BundleInternalExtras.PICK_DOCUMENT) as DocumentFilePickerConfig? - } + BundleCompat.getParcelable( + intent.extras ?: Bundle.EMPTY, + Const.BundleInternalExtras.PICK_DOCUMENT, + DocumentFilePickerConfig::class.java + ) } val documentFilePickerLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> - setActivityResult( - resultCode = result.resultCode, - resultIntent = result.data, - false - ) + if (result.resultCode == RESULT_OK && result.data != null) { + if (mDocumentFilePickerConfig?.allowMultiple == true && result.data?.clipData != null) { + val uris = result.data?.getClipDataUris() + val filePaths = uris?.getFilePathList(this) + setSuccessResult(uris, filePath = filePaths) + } else if (result.data?.data != null) { + val data = result.data?.data + val filePath = data?.let { FileUtils.getRealPath(this, it) } + setSuccessResult(data, filePath) + } else if (result.data?.clipData != null) { + val uri = result.data?.getClipDataUris()?.firstOrNull() + val filePath = uri?.let { FileUtils.getRealPath(this, it) } + setSuccessResult(uri, filePath) + } else { + setCanceledResult(getString(R.string.document_file_picker_no_data_error)) + } + } else { + setCanceledResult("File Picker Result Error: ${result.resultCode}") + } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() if (mDocumentFilePickerConfig == null) { setCanceledResult(getString(R.string.document_file_picker_config_null_error)) return diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/ImageCaptureActivity.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/ImageCaptureActivity.kt index e7fd261..e5f30e9 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/ImageCaptureActivity.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/ImageCaptureActivity.kt @@ -3,10 +3,10 @@ package com.nareshchocha.filepickerlibrary.ui.activitys import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -15,6 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.core.os.BundleCompat import com.nareshchocha.filepickerlibrary.R import com.nareshchocha.filepickerlibrary.models.ImageCaptureConfig import com.nareshchocha.filepickerlibrary.ui.components.dialogs.AppRationaleDialog @@ -34,15 +35,11 @@ import java.io.File internal class ImageCaptureActivity : ComponentActivity() { private val mImageCaptureConfig: ImageCaptureConfig? by lazy { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra( - Const.BundleInternalExtras.IMAGE_CAPTURE, - ImageCaptureConfig::class.java - ) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(Const.BundleInternalExtras.IMAGE_CAPTURE) as ImageCaptureConfig? - } + BundleCompat.getParcelable( + intent.extras ?: Bundle.EMPTY, + Const.BundleInternalExtras.IMAGE_CAPTURE, + ImageCaptureConfig::class.java + ) } private var imageFile: File? = null private val imageFileUri: Uri? by lazy { @@ -79,6 +76,7 @@ internal class ImageCaptureActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() if (mImageCaptureConfig == null) { imageFile?.delete() setCanceledResult(getString(R.string.image_capture_config_null_error)) diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/MediaFilePickerActivity.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/MediaFilePickerActivity.kt index 75aefda..f33d4ac 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/MediaFilePickerActivity.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/MediaFilePickerActivity.kt @@ -1,11 +1,12 @@ package com.nareshchocha.filepickerlibrary.ui.activitys +import android.app.Activity import android.content.Context import android.content.Intent -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -14,41 +15,60 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.core.os.BundleCompat import com.nareshchocha.filepickerlibrary.R import com.nareshchocha.filepickerlibrary.models.PickMediaConfig import com.nareshchocha.filepickerlibrary.ui.components.dialogs.AppRationaleDialog import com.nareshchocha.filepickerlibrary.ui.components.dialogs.AppSettingDialog +import com.nareshchocha.filepickerlibrary.utilities.FileUtils import com.nareshchocha.filepickerlibrary.utilities.MediaMultiplePermissionManager import com.nareshchocha.filepickerlibrary.utilities.PermissionLists import com.nareshchocha.filepickerlibrary.utilities.appConst.Const import com.nareshchocha.filepickerlibrary.utilities.extensions.asString import com.nareshchocha.filepickerlibrary.utilities.extensions.getActivityOrNull +import com.nareshchocha.filepickerlibrary.utilities.getClipDataUris +import com.nareshchocha.filepickerlibrary.utilities.getFilePathList import com.nareshchocha.filepickerlibrary.utilities.getMediaIntent -import com.nareshchocha.filepickerlibrary.utilities.setActivityResult import com.nareshchocha.filepickerlibrary.utilities.setCanceledResult +import com.nareshchocha.filepickerlibrary.utilities.setSuccessResult internal class MediaFilePickerActivity : ComponentActivity() { private val mPickMediaConfig: PickMediaConfig? by lazy { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra( - Const.BundleInternalExtras.PICK_MEDIA, - PickMediaConfig::class.java - ) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(Const.BundleInternalExtras.PICK_MEDIA) as PickMediaConfig? - } + BundleCompat.getParcelable( + intent.extras ?: Bundle.EMPTY, + Const.BundleInternalExtras.PICK_MEDIA, + PickMediaConfig::class.java + ) } val mediaFilePickerLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> - setActivityResult(result.resultCode, result.data, false) + if (result.resultCode == Activity.RESULT_OK && result.data != null) { + if (mPickMediaConfig?.allowMultiple == true && result.data?.clipData != null) { + val uris = result.data?.getClipDataUris() + val filePaths = uris?.getFilePathList(this) + setSuccessResult(uris, filePath = filePaths) + } else if (result.data?.data != null) { + val data = result.data?.data + val filePath = data?.let { FileUtils.getRealPath(this, it) } + setSuccessResult(data, filePath) + } else if (result.data?.clipData != null) { + val uri = result.data?.getClipDataUris()?.firstOrNull() + val filePath = uri?.let { FileUtils.getRealPath(this, it) } + setSuccessResult(uri, filePath) + } else { + setCanceledResult(getString(R.string.media_file_picker_no_data_error)) + } + } else { + setCanceledResult("File Picker Result Error: ${result.resultCode}") + } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() if (mPickMediaConfig == null) { setCanceledResult(getString(R.string.media_file_picker_config_null_error)) return diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/PopUpActivity.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/PopUpActivity.kt index 493ae9e..4abafbe 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/PopUpActivity.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/PopUpActivity.kt @@ -2,10 +2,10 @@ package com.nareshchocha.filepickerlibrary.ui.activitys import android.content.Context import android.content.Intent -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column @@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.core.os.BundleCompat import com.nareshchocha.filepickerlibrary.FilePickerResultContracts import com.nareshchocha.filepickerlibrary.R import com.nareshchocha.filepickerlibrary.models.BaseConfig @@ -38,15 +39,11 @@ import com.nareshchocha.filepickerlibrary.utilities.toArrayList internal class PopUpActivity : ComponentActivity() { private val mPickerData: PickerData? by lazy { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra( - Const.BundleInternalExtras.PICKER_DATA, - PickerData::class.java - ) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(Const.BundleInternalExtras.PICKER_DATA) as PickerData? - } + BundleCompat.getParcelable( + intent.extras ?: Bundle.EMPTY, + Const.BundleInternalExtras.PICKER_DATA, + PickerData::class.java + ) } private val intentResultLauncher = @@ -68,6 +65,7 @@ internal class PopUpActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() setContent { val pickerData = mPickerData if (pickerData == null) { diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/VideoCaptureActivity.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/VideoCaptureActivity.kt index 62efeef..bae1240 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/VideoCaptureActivity.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/ui/activitys/VideoCaptureActivity.kt @@ -3,10 +3,10 @@ package com.nareshchocha.filepickerlibrary.ui.activitys import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -15,6 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.core.os.BundleCompat import com.nareshchocha.filepickerlibrary.R import com.nareshchocha.filepickerlibrary.models.VideoCaptureConfig import com.nareshchocha.filepickerlibrary.ui.components.dialogs.AppRationaleDialog @@ -34,15 +35,11 @@ import java.io.File internal class VideoCaptureActivity : ComponentActivity() { private val mVideoCaptureConfig: VideoCaptureConfig? by lazy { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra( - Const.BundleInternalExtras.VIDEO_CAPTURE, - VideoCaptureConfig::class.java - ) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(Const.BundleInternalExtras.VIDEO_CAPTURE) as VideoCaptureConfig? - } + BundleCompat.getParcelable( + intent.extras ?: Bundle.EMPTY, + Const.BundleInternalExtras.VIDEO_CAPTURE, + VideoCaptureConfig::class.java + ) } private var videoFile: File? = null private val videoFileUri: Uri? by lazy { @@ -77,6 +74,7 @@ internal class VideoCaptureActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() if (mVideoCaptureConfig == null) { videoFile?.delete() setCanceledResult(getString(R.string.image_capture_config_null_error)) diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FilePickerResultHandler.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FilePickerResultHandler.kt index 52bc793..408a897 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FilePickerResultHandler.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FilePickerResultHandler.kt @@ -82,27 +82,6 @@ internal fun Activity.setSuccessResult( finish() } -internal fun Activity.setActivityResult( - resultCode: Int? = null, - resultIntent: Intent? = null, - isFromCapture: Boolean = false -) { - log("ResultIntent : $resultIntent", LogPriority.INFO_LOG, Const.LogTag.FILE_PICKER_RESULT) - setResult( - if (resultIntent == null || resultCode == null) Activity.RESULT_CANCELED else resultCode, - resultIntent?.apply { - flags = - if (isFromCapture) { - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION - } else { - Intent.FLAG_GRANT_READ_URI_PERMISSION - } - if (isFromCapture) putExtra(Const.BundleExtras.FROM_CAPTURE, true) - } - ) - finish() -} - internal fun Activity.setCanceledResult(error: String? = null) { log("Error: $error", LogPriority.ERROR_LOG, Const.LogTag.FILE_PICKER_RESULT) setResult( diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FileUtils.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FileUtils.kt index 529deac..5e5a3ea 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FileUtils.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/FileUtils.kt @@ -7,7 +7,6 @@ import android.net.Uri import android.os.Environment import android.provider.DocumentsContract import android.provider.MediaStore -import androidx.annotation.Keep import androidx.core.net.toUri import com.nareshchocha.filepickerlibrary.utilities.appConst.Const import com.nareshchocha.filepickerlibrary.utilities.extensions.copyFileToInternalStorage @@ -21,9 +20,7 @@ import com.nareshchocha.filepickerlibrary.utilities.extensions.isGooglePhotosUri import com.nareshchocha.filepickerlibrary.utilities.extensions.isMediaDocument import java.io.File -@Keep internal object FileUtils { - @Keep fun getRealPath( context: Context, fileUri: Uri @@ -48,7 +45,6 @@ internal object FileUtils { null } - @Keep private fun pathFromURI( context: Context, uri: Uri @@ -126,7 +122,6 @@ internal object FileUtils { } } - @Keep private fun Context.getDownloadsDocumentPath(uri: Uri): String? { /*val fileName = getFileName(this, uri) if (fileName != null) { @@ -164,7 +159,6 @@ internal object FileUtils { return filePath ?: uri.path?.replaceFirst("^/document/raw:", "")?.replaceFirst("^raw:", "") } - @Keep private fun getExternalDocumentPath(uri: Uri): String { val docId = DocumentsContract.getDocumentId(uri) val split = @@ -186,7 +180,6 @@ internal object FileUtils { } } - @Keep private fun getFileName( context: Context, uri: Uri? diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/PickerFileUtils.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/PickerFileUtils.kt index fa0979e..69d72c9 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/PickerFileUtils.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/PickerFileUtils.kt @@ -2,7 +2,6 @@ package com.nareshchocha.filepickerlibrary.utilities import android.content.Context import android.net.Uri -import androidx.annotation.Keep import androidx.core.content.FileProvider import com.nareshchocha.filepickerlibrary.utilities.appConst.Const import java.io.File @@ -20,7 +19,6 @@ internal object PickerFileUtils { * @param fileName The name of the file to be created * @return A File object representing the newly created file path */ - @Keep fun createMediaFileFolder( folderFile: File, fileName: String @@ -64,7 +62,6 @@ internal object PickerFileUtils { * @param mFile The File object to create and get URI for * @return A content URI for the file or null if creation or URI generation fails */ - @Keep fun Context.createFileGetUri(mFile: File?): Uri? { if (mFile == null) { log("File is null, cannot create or get URI", priority = LogPriority.ERROR_LOG) diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/appConst/Const.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/appConst/Const.kt index e1e1586..3546f8f 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/appConst/Const.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/appConst/Const.kt @@ -2,14 +2,12 @@ package com.nareshchocha.filepickerlibrary.utilities.appConst import android.content.Context import android.os.Environment -import androidx.annotation.Keep import com.nareshchocha.filepickerlibrary.R import java.io.File /** * Contains constants and utility objects used throughout the File Picker library. */ -@Keep internal object Const { /** FileProvider authority suffix. */ const val AUTHORITY = ".library.fileprovider" @@ -85,7 +83,6 @@ internal object Const { /** * Keys for bundle extras exposed to external consumers. */ - @Keep object BundleExtras { /** Indicates if the file is from capture. */ const val FROM_CAPTURE = "isFromCapture" diff --git a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/extensions/FilePathExtensions.kt b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/extensions/FilePathExtensions.kt index 7d100dc..053be38 100644 --- a/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/extensions/FilePathExtensions.kt +++ b/filepickerlibrary/src/main/java/com/nareshchocha/filepickerlibrary/utilities/extensions/FilePathExtensions.kt @@ -6,7 +6,6 @@ import android.net.Uri import android.provider.DocumentsContract import android.provider.MediaStore import android.provider.OpenableColumns -import androidx.annotation.Keep import com.nareshchocha.filepickerlibrary.utilities.LogPriority import com.nareshchocha.filepickerlibrary.utilities.appConst.Const import com.nareshchocha.filepickerlibrary.utilities.log @@ -39,7 +38,6 @@ internal fun Uri.isGoogleDriveUri(): Boolean = "com.google.android.apps.docs.storage" == authority || "com.google.android.apps.docs.storage.legacy" == authority -@Keep internal fun getDataColumn( context: Context, uri: Uri?, @@ -71,7 +69,6 @@ internal fun getDataColumn( return null } -@Keep internal fun Context.getMediaDocumentPath(uri: Uri): String? { val docId = DocumentsContract.getDocumentId(uri) val split = diff --git a/filepickerlibrary/src/main/res/values/strings.xml b/filepickerlibrary/src/main/res/values/strings.xml index d41f29f..2eead2e 100644 --- a/filepickerlibrary/src/main/res/values/strings.xml +++ b/filepickerlibrary/src/main/res/values/strings.xml @@ -48,9 +48,11 @@ The document picker configuration is missing. This app requires access to your storage in order to pick and manage documents. Please grant the storage permission to proceed. + No Document files found in the selected directory. The media file picker configuration is missing. This app requires access to your storage to pick media files such as images and videos. Please grant the storage permission to continue. + No media files found in the selected directory. \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5a8e0c..d11a880 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -agp = "8.10.1" -mavenPublish = "0.25.3" -kotlin = "2.1.20" +agp = "8.12.0" +mavenPublish = "0.34.0" +kotlin = "2.2.0" detekt = "1.23.8" # build config @@ -15,17 +15,7 @@ core-splashscreen="1.0.1" # compose activityCompose = "1.10.1" -composeBom = "2025.06.01" - -# Testing -junit = "4.13.2" -junitVersion = "1.2.1" -espressoCore = "3.6.1" - - -# testing -truth = "1.1.5" -lifecycleRuntimeKtx = "2.9.1" +composeBom = "2025.07.00" [libraries] # core @@ -40,17 +30,9 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } -# testing -junit = { group = "junit", name = "junit", version.ref = "junit" } -androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } # testing compose -androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -# testing -truth = { module = "com.google.truth:truth", version.ref = "truth" } -androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -62,7 +44,7 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } # code style review arturbosch-detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } -spotless = { id = "com.diffplug.spotless", version = "7.0.4" } +spotless = { id = "com.diffplug.spotless", version = "7.2.1" } # maven publish maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } # signing diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b6571f8..51dfbfe 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Nov 22 15:17:24 IST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample/.gitignore b/sample/.gitignore index 42afabf..0b0a500 100644 --- a/sample/.gitignore +++ b/sample/.gitignore @@ -1 +1,3 @@ -/build \ No newline at end of file +/build +/certificates +/release \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index e420dab..dbb4ba7 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -22,21 +24,32 @@ android { libs.versions.targetSdk .get() .toInt() - versionCode = 2 - versionName = "1.1" + versionCode = 4 + versionName = "0.7.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } - + packaging { + jniLibs { + excludes.add("lib/arm64-v8a/libandroidx.graphics.path.so") + excludes.add("lib/armeabi-v7a/libandroidx.graphics.path.so") + excludes.add("lib/x86/libandroidx.graphics.path.so") + excludes.add("lib/x86_64/libandroidx.graphics.path.so") + } + } buildTypes { release { - isMinifyEnabled = false + isMinifyEnabled = true + isShrinkResources = true + isJniDebuggable = false + isDebuggable = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } + buildFeatures { compose = true aidl = false @@ -48,9 +61,10 @@ android { sourceCompatibility = JavaVersion.valueOf(libs.versions.jdkVersion.get()) targetCompatibility = JavaVersion.valueOf(libs.versions.jdkVersion.get()) } - - kotlinOptions { - jvmTarget = JavaVersion.valueOf(libs.versions.jdkVersion.get()).toString() + kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } } } @@ -67,20 +81,13 @@ dependencies { implementation(libs.androidx.material.icons.extended) implementation("io.coil-kt.coil3:coil-compose:3.2.0") + // File Picker implementation(project(":filepickerlibrary")) - implementation(libs.androidx.lifecycle.runtime.ktx) - - // testing - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) - testImplementation(libs.truth) - androidTestImplementation(libs.truth) + // implementation(libs.androidx.lifecycle.runtime.ktx) // testing compose androidTestImplementation(platform(libs.androidx.compose.bom)) - androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) } diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro index ff59496..1a4f7ea 100644 --- a/sample/proguard-rules.pro +++ b/sample/proguard-rules.pro @@ -1,21 +1 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle.kts. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +-keep,allowobfuscation class com.nareshchocha.filepicker.** {*;} \ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 4ee0712..4800817 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.FilePicker.Splash" @@ -30,6 +31,7 @@ + \ No newline at end of file diff --git a/sample/src/main/java/com/nareshchocha/filepicker/RootActivity.kt b/sample/src/main/java/com/nareshchocha/filepicker/RootActivity.kt index 592d7ec..33ecbfd 100644 --- a/sample/src/main/java/com/nareshchocha/filepicker/RootActivity.kt +++ b/sample/src/main/java/com/nareshchocha/filepicker/RootActivity.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import com.nareshchocha.filepicker.components.AllFilePicker +import com.nareshchocha.filepicker.components.AllFilePickers import com.nareshchocha.filepicker.ui.theme.FilePickerTheme import com.nareshchocha.filepickerlibrary.FilePickerResultContracts @@ -73,7 +73,7 @@ fun RootUI() { .fillMaxSize() .padding(innerPadding) ) { - AllFilePicker() + AllFilePickers() } } } diff --git a/sample/src/main/java/com/nareshchocha/filepicker/components/AppFilePickerResult.kt b/sample/src/main/java/com/nareshchocha/filepicker/components/AppFilePickerResult.kt index 321e9fd..d0d6c9b 100644 --- a/sample/src/main/java/com/nareshchocha/filepicker/components/AppFilePickerResult.kt +++ b/sample/src/main/java/com/nareshchocha/filepicker/components/AppFilePickerResult.kt @@ -1,113 +1,87 @@ package com.nareshchocha.filepicker.components -import android.util.Log +import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text 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.tooling.preview.Preview +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.nareshchocha.filepickerlibrary.FilePickerResultContracts import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig @Composable -fun FilePickerCaptureImage() { +fun AllFilePickers() { + var pickedFiles by remember { mutableStateOf(listOf()) } + val context = LocalContext.current + + // Helper to add a file to the list + fun addPickedFile( + uri: Uri?, + path: String? + ) { + uri ?: return + val mimeType = context.contentResolver.getType(uri) + val type = + when { + mimeType?.startsWith("image") == true -> "image" + mimeType?.startsWith("video") == true -> "video" + else -> "other" + } + pickedFiles = pickedFiles + PickedFile(uri, type, path) + } + val captureImageResultLauncher = rememberLauncherForActivityResult(FilePickerResultContracts.ImageCapture()) { result -> - Log.d("FilePicker", "Image Capture Result: $result") + addPickedFile(result?.selectedFileUri, result?.selectedFilePath) } - AppButton( - text = "Capture Image", - onClick = { - captureImageResultLauncher.launch(null) - } - ) -} - -@Composable -fun FilePickerCaptureVideo() { val captureVideoResultLauncher = rememberLauncherForActivityResult(FilePickerResultContracts.VideoCapture()) { result -> - Log.d("FilePicker", "Video Capture Result: $result") + addPickedFile(result?.selectedFileUri, result?.selectedFilePath) } - AppButton( - text = "Capture Video", - onClick = { - captureVideoResultLauncher.launch(null) - } - ) -} - -@Composable -fun FilePickerPickImage() { val pickImageResultLauncher = rememberLauncherForActivityResult(FilePickerResultContracts.PickMedia()) { result -> - Log.d("FilePicker", "Pick Image Result: $result") + addPickedFile(result?.selectedFileUri, result?.selectedFilePath) } - AppButton( - text = "Pick Image", - onClick = { - pickImageResultLauncher.launch(null) - } - ) -} - -@Composable -fun FilePickerPickDocument() { val pickDocumentResultLauncher = rememberLauncherForActivityResult(FilePickerResultContracts.PickDocumentFile()) { result -> - Log.d("FilePicker", "Pick Document Result: $result") + addPickedFile(result?.selectedFileUri, result?.selectedFilePath) } - AppButton( - text = "Pick Document", - onClick = { - pickDocumentResultLauncher.launch(null) - } - ) -} - -@Composable -fun FilePickerAllFilePicker() { - val pickDocumentResultLauncher = + val pickAllFilesResultLauncher = rememberLauncherForActivityResult(FilePickerResultContracts.AllFilePicker()) { result -> - Log.d("FilePicker", "Pick All Files Result: $result") - } - AppButton( - text = "Pick All Files", - onClick = { - pickDocumentResultLauncher.launch(null) + addPickedFile(result?.selectedFileUri, result?.selectedFilePath) } - ) -} - -@Composable -fun FilePickerAnyFilePicker() { - val pickDocumentResultLauncher = + val pickAnyFileResultLauncher = rememberLauncherForActivityResult(FilePickerResultContracts.AnyFilePicker()) { result -> - Log.d("FilePicker", "Pick Any File Result: $result") + addPickedFile(result?.selectedFileUri, result?.selectedFilePath) } - AppButton( - text = "Pick Any File", - onClick = { - pickDocumentResultLauncher.launch(DocumentFilePickerConfig()) - } - ) -} -@Preview -@Composable -fun AllFilePicker() { Column( modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - FilePickerCaptureImage() - FilePickerCaptureVideo() - FilePickerPickImage() - FilePickerPickDocument() - FilePickerAllFilePicker() - FilePickerAnyFilePicker() + AppButton("Capture Image") { captureImageResultLauncher.launch(null) } + AppButton("Capture Video") { captureVideoResultLauncher.launch(null) } + AppButton("Pick Image") { pickImageResultLauncher.launch(null) } + AppButton("Pick Document") { pickDocumentResultLauncher.launch(null) } + AppButton("Pick All Files") { pickAllFilesResultLauncher.launch(null) } + AppButton("Pick Any File") { pickAnyFileResultLauncher.launch(DocumentFilePickerConfig()) } + + if (pickedFiles.isNotEmpty()) { + Spacer(Modifier.height(8.dp)) + Text("Selected Files:", style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold)) + FilePickerWithResultList(pickedFiles) + } } } diff --git a/sample/src/main/java/com/nareshchocha/filepicker/components/FilePickerWithResultList.kt b/sample/src/main/java/com/nareshchocha/filepicker/components/FilePickerWithResultList.kt new file mode 100644 index 0000000..9e1a365 --- /dev/null +++ b/sample/src/main/java/com/nareshchocha/filepicker/components/FilePickerWithResultList.kt @@ -0,0 +1,102 @@ +package com.nareshchocha.filepicker.components + +import android.net.Uri +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Videocam +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil3.compose.rememberAsyncImagePainter + +@Composable +fun FilePickerWithResultList(pickedFiles: List) { + LazyColumn( + contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxHeight() + ) { + items(pickedFiles) { file -> + when (file.type) { + "image" -> + Image( + painter = rememberAsyncImagePainter(file.uri), + contentDescription = null, + modifier = Modifier.size(100.dp), + contentScale = ContentScale.Crop + ) + + "video" -> { + Row( + modifier = + Modifier + .background( + color = MaterialTheme.colorScheme.secondaryContainer, + shape = RoundedCornerShape(8.dp) + ).padding(12.dp) + ) { + Icon( + imageVector = Icons.Default.Videocam, + contentDescription = "Video", + tint = MaterialTheme.colorScheme.outline, + modifier = Modifier.padding(end = 8.dp) + ) + Text( + text = "Video: ${file.uri}", + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) + ) + } + } + + else -> { + Column( + modifier = + Modifier + .background( + color = MaterialTheme.colorScheme.outline, + shape = RoundedCornerShape(8.dp) + ).padding(12.dp) + ) { + Text( + text = "URI: ${file.uri}", + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) + ) + Text( + text = "Path: ${file.filePath ?: "N/A"}", + style = MaterialTheme.typography.bodySmall + ) + } + } + } + } + } +} + +data class PickedFile( + val uri: Uri, + val type: String, // "image", "video", "other" + val filePath: String? = null +) + +@Preview +@Composable +fun PreviewFilePickerWithResultList() { + FilePickerWithResultList(emptyList()) +} diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 5589fa3..9e01733 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - FilePicker + File Picker Capture Image Capture Video Pick Images diff --git a/sample/src/test/java/com/nareshchocha/filepicker/ExampleUnitTest.kt b/sample/src/test/java/com/nareshchocha/filepicker/ExampleUnitTest.kt deleted file mode 100644 index a1e6f29..0000000 --- a/sample/src/test/java/com/nareshchocha/filepicker/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.nareshchocha.filepicker - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -}