diff --git a/build.gradle b/build.gradle deleted file mode 100644 index b0e0fdee..00000000 --- a/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - apply from: 'versions.gradle' - - addRepos(repositories) - - ext.kotlin_version = '2.0.0' - - dependencies { - classpath 'com.android.tools.build:gradle:8.4.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.vanniktech:gradle-maven-publish-plugin:0.22.0' - classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.7.20' - } -} - -allprojects { - addRepos(repositories) - - //Support @JvmDefault - tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { - kotlinOptions { - freeCompilerArgs = ['-Xjvm-default=all', '-opt-in=kotlin.RequiresOptIn'] - jvmTarget = '1.8' - } - } -} - -subprojects { - if (name != 'sample') { - apply plugin: "com.vanniktech.maven.publish" - - repositories { - maven { - url = version.toString().endsWith("SNAPSHOT") - ? 'https://oss.sonatype.org/content/repositories/snapshots/' - : 'https://oss.sonatype.org/service/local/staging/deploy/maven2' - } - } - } - - afterEvaluate { - android { - compileSdkVersion 34 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 34 - versionCode 1 - versionName "$VERSION_NAME" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables.useSupportLibrary = true - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - buildConfig true - } - } - configurations.configureEach { - resolutionStrategy { - // Force Kotlin to use current version - force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - force "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" - force "org.jetbrains.kotlin:kotlin-android-extensions-runtime:$kotlin_version" - } - } - // global dependencies for all modules - dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" - } - } -} - -tasks.register('clean', Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..844615f1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,6 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false +} diff --git a/gradle.properties b/gradle.properties index 84e697f6..e8e8cba7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,10 +19,11 @@ android.useAndroidX=true android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official +org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers # For publishing: GROUP=com.anggrayudi POM_ARTIFACT_ID=storage -VERSION_NAME=2.0.0-SNAPSHOT +VERSION_NAME=2.1.0-SNAPSHOT RELEASE_SIGNING_ENABLED=false SONATYPE_AUTOMATIC_RELEASE=true SONATYPE_HOST=DEFAULT diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..89b2d466 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,55 @@ +[versions] +kotlin = "2.1.20" +activityCompose = "1.10.1" +coroutines = "1.10.2" +mockito = "3.10.0" +powermock = "2.0.9" + +[libraries] +androidx-core = { group = "androidx.core", name = "core-ktx", version = "1.16.0" } +junit = { group = "junit", name = "junit", version = "4.13.2" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version = "1.2.1" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version = "3.6.1" } +androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version = "2.9.1" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version = "1.7.1" } +androidx-activity = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityCompose" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version = "2025.06.00" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-multidex = { group = "androidx.multidex", name = "multidex", version = "2.0.1" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version = "2.9.0" } +androidx-datastore = { group = "androidx.datastore", name = "datastore-preferences-android", version = "1.1.7" } +androidx-preference = { group = "androidx.preference", name = "preference-ktx", version = "1.2.1" } +androidx-document-file = { group = "androidx.documentfile", name = "documentfile", version = "1.1.0" } +androidx-fragment = { group = "androidx.fragment", name = "fragment-ktx", version = "1.8.8" } +material-icons-ext = { group = "androidx.compose.material", name = "material-icons-extended", version = "1.7.8" } + +material-dialogs-files = { group = "com.afollestad.material-dialogs", name = "files", version = "3.3.0" } +material-progress-bar = { group = "me.zhanghai.android.materialprogressbar", name = "library", version = "1.6.1" } +timber = { group = "com.jakewharton.timber", name = "timber", version = "5.0.1" } +coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } +coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } + +mockk = { group = "io.mockk", name = "mockk", version = "1.13.17" } +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } +robolectric = { group = "org.robolectric", name = "robolectric", version = "4.10.3" } +mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } +mockito-inline = { group = "org.mockito", name = "mockito-inline", version.ref = "mockito" } +mockito-all = { group = "org.mockito", name = "mockito-all", version = "1.10.19" } +mockito-kotlin = { group = "com.nhaarman.mockitokotlin2", name = "mockito-kotlin", version = "2.2.0" } +powermock-junit4 = { group = "org.powermock", name = "powermock-module-junit4", version.ref = "powermock" } +powermock-api-mockito = { group = "org.powermock", name = "powermock-api-mockito2", version.ref = "powermock" } + +[plugins] +android-application = { id = "com.android.application", version = "8.9.3" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version = "2.1.20-1.0.32" } +maven-publish = { id = "com.vanniktech.maven.publish", version = "0.22.0" } +dokka = { id = "org.jetbrains.dokka", version = "2.0.0" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index befbbe69..343461a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Dec 01 18:57:30 WIB 2020 +#Tue Jun 10 19:14:26 WIB 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip diff --git a/sample/build.gradle b/sample/build.gradle deleted file mode 100644 index a9e7c861..00000000 --- a/sample/build.gradle +++ /dev/null @@ -1,87 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -def properties = new Properties() -properties.load(rootProject.file('local.properties').newDataInputStream()) - -android { - signingConfigs { - def debugKeystore = file("${System.properties['user.home']}${File.separator}.android${File.separator}debug.keystore") - debug { - keyAlias 'androiddebugkey' - keyPassword 'android' - storePassword 'android' - storeFile debugKeystore - } - release { - keyAlias 'androiddebugkey' - keyPassword 'android' - storePassword 'android' - storeFile debugKeystore - } - } - - namespace 'com.anggrayudi.storage.sample' - defaultConfig { - applicationId "com.anggrayudi.storage.sample" - multiDexEnabled true - } - - buildTypes { - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.debug - } - release { - minifyEnabled true - shrinkResources false - zipAlignEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.release - } - } - - lint { - abortOnError false - } - - buildFeatures { - viewBinding = true - } - - flavorDimensions "libSource" - productFlavors { - local { - dimension "libSource" - getIsDefault().set(true) - } - maven { - dimension "libSource" - configurations.all { - // Check for updates every build - resolutionStrategy.cacheChangingModulesFor 0, 'seconds' - } - } - } -} - -dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation project(":storage") -// localImplementation project(":storage") -// mavenImplementation("$GROUP:$POM_ARTIFACT_ID:$VERSION_NAME") { changing = true } - - implementation deps.core_ktx - implementation deps.appcompat - implementation deps.multidex - - implementation deps.timber - implementation deps.material_progressbar - implementation 'androidx.preference:preference-ktx:1.2.1' - implementation 'com.afollestad.material-dialogs:files:3.3.0' - - //test - testImplementation deps.junit - testImplementation deps.mockk -} \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts new file mode 100644 index 00000000..9fb955ed --- /dev/null +++ b/sample/build.gradle.kts @@ -0,0 +1,115 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.ksp) +} + +android { + namespace = "com.anggrayudi.storage.sample" + compileSdk = 35 + + signingConfigs { + val debugKeystore = + file( + "${System.getProperty("user.home")}${File.separator}.android${File.separator}debug.keystore" + ) + getByName("debug") { + keyAlias = "androiddebugkey" + keyPassword = "android" + storePassword = "android" + storeFile = debugKeystore + } + create("release") { + keyAlias = "androiddebugkey" + keyPassword = "android" + storePassword = "android" + storeFile = debugKeystore + } + } + + defaultConfig { + applicationId = "com.anggrayudi.storage.sample" + minSdk = 21 + targetSdk = 35 + versionCode = 1 + versionName = rootProject.extra["VERSION_NAME"] as String + multiDexEnabled = true + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + debug { signingConfig = signingConfigs.getByName("debug") } + release { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + signingConfig = signingConfigs.getByName("release") + } + } + + buildFeatures { + viewBinding = true + compose = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { jvmTarget = "11" } + + flavorDimensions += "libSource" + productFlavors { + create("local") { dimension = "libSource" } + create("maven") { + dimension = "libSource" + configurations.all { + // Check for updates every build + resolutionStrategy.cacheChangingModulesFor(0, TimeUnit.SECONDS) + } + } + } + + applicationVariants.forEach { variant -> + variant.sourceSets.forEach { + it.javaDirectories += files("build/generated/ksp/${variant.name}/kotlin") + } + } +} + +dependencies { + implementation(project(":storage")) + // implementation("com.anggrayudi:storage:${rootProject.extra["VERSION_NAME"]}") + + implementation(libs.androidx.core) + implementation(libs.androidx.lifecycle.runtime) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.androidx.multidex) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.datastore) + implementation(libs.androidx.preference) + implementation(libs.material.icons.ext) + + implementation(libs.timber) + implementation(libs.coroutines.android) + implementation(libs.material.progress.bar) + implementation(libs.material.dialogs.files) + + testImplementation(libs.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.mockk) + testImplementation(libs.kotlin.test) + + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + 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/src/main/java/com/anggrayudi/storage/sample/App.kt b/sample/src/main/java/com/anggrayudi/storage/sample/App.kt index ee8dbbc7..938a34d2 100644 --- a/sample/src/main/java/com/anggrayudi/storage/sample/App.kt +++ b/sample/src/main/java/com/anggrayudi/storage/sample/App.kt @@ -9,8 +9,8 @@ import timber.log.Timber */ class App : MultiDexApplication() { - override fun onCreate() { - super.onCreate() - Timber.plant(Timber.DebugTree()) - } -} \ No newline at end of file + override fun onCreate() { + super.onCreate() + Timber.plant(Timber.DebugTree()) + } +} diff --git a/sample/src/main/java/com/anggrayudi/storage/sample/StorageInfoAdapter.kt b/sample/src/main/java/com/anggrayudi/storage/sample/StorageInfoAdapter.kt index 8f4b2976..2c82b626 100644 --- a/sample/src/main/java/com/anggrayudi/storage/sample/StorageInfoAdapter.kt +++ b/sample/src/main/java/com/anggrayudi/storage/sample/StorageInfoAdapter.kt @@ -19,71 +19,75 @@ import kotlinx.coroutines.launch /** * Created on 12/14/20 + * * @author Anggrayudi H */ class StorageInfoAdapter( - private val context: Context, - private val ioScope: CoroutineScope, - private val uiScope: CoroutineScope + private val context: Context, + private val ioScope: CoroutineScope, + private val uiScope: CoroutineScope, ) : RecyclerView.Adapter() { - private val storageIds = DocumentFileCompat.getStorageIds(context) + private val storageIds = DocumentFileCompat.getStorageIds(context) - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_item_storage_info, parent, false)) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.view_item_storage_info, parent, false) + ) + } - @SuppressLint("SetTextI18n") - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - ioScope.launch { - val storageId = storageIds[position] - val storageName = if (storageId == PRIMARY) "External Storage" else storageId - val storageCapacity = Formatter.formatFileSize(context, DocumentFileCompat.getStorageCapacity(context, storageId)) - val storageUsedSpace = Formatter.formatFileSize(context, DocumentFileCompat.getUsedSpace(context, storageId)) - val storageFreeSpace = Formatter.formatFileSize(context, DocumentFileCompat.getFreeSpace(context, storageId)) - uiScope.launch { - holder.run { - tvStorageName.text = storageName - tvStorageCapacity.text = "Capacity: $storageCapacity" - tvStorageUsedSpace.text = "Used Space: $storageUsedSpace" - tvStorageFreeSpace.text = "Free Space: $storageFreeSpace" - btnShowGrantedUri.setOnClickListener { showGrantedUris(it.context, storageId) } - if (storageId == PRIMARY && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - // No URI permission required for external storage - btnShowGrantedUri.visibility = View.GONE - } - } - } + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + ioScope.launch { + val storageId = storageIds[position] + val storageName = if (storageId == PRIMARY) "External Storage" else storageId + val storageCapacity = + Formatter.formatFileSize(context, DocumentFileCompat.getStorageCapacity(context, storageId)) + val storageUsedSpace = + Formatter.formatFileSize(context, DocumentFileCompat.getUsedSpace(context, storageId)) + val storageFreeSpace = + Formatter.formatFileSize(context, DocumentFileCompat.getFreeSpace(context, storageId)) + uiScope.launch { + holder.run { + tvStorageName.text = storageName + tvStorageCapacity.text = "Capacity: $storageCapacity" + tvStorageUsedSpace.text = "Used Space: $storageUsedSpace" + tvStorageFreeSpace.text = "Free Space: $storageFreeSpace" + btnShowGrantedUri.setOnClickListener { showGrantedUris(it.context, storageId) } + if (storageId == PRIMARY && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + // No URI permission required for external storage + btnShowGrantedUri.visibility = View.GONE + } } + } } + } - /** - * A storageId may contains more than one granted URIs - */ - @SuppressLint("NewApi") - private fun showGrantedUris(context: Context, filterStorageId: String) { - val grantedPaths = DocumentFileCompat.getAccessibleAbsolutePaths(context)[filterStorageId] - if (grantedPaths == null) { - MaterialDialog(context) - .message(text = "No permission granted on storage ID \"$filterStorageId\"") - .positiveButton() - .show() - } else { - MaterialDialog(context) - .title(text = "Granted paths for \"$filterStorageId\"") - .listItems(items = grantedPaths.toList().sorted()) - .show() - } + /** A storageId may contains more than one granted URIs */ + @SuppressLint("NewApi") + private fun showGrantedUris(context: Context, filterStorageId: String) { + val grantedPaths = DocumentFileCompat.getAccessibleAbsolutePaths(context)[filterStorageId] + if (grantedPaths == null) { + MaterialDialog(context) + .message(text = "No permission granted on storage ID \"$filterStorageId\"") + .positiveButton() + .show() + } else { + MaterialDialog(context) + .title(text = "Granted paths for \"$filterStorageId\"") + .listItems(items = grantedPaths.toList().sorted()) + .show() } + } - override fun getItemCount() = storageIds.size + override fun getItemCount() = storageIds.size - class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - internal val tvStorageName = view.findViewById(R.id.tvStorageName) - internal val tvStorageCapacity = view.findViewById(R.id.tvStorageCapacity) - internal val tvStorageUsedSpace = view.findViewById(R.id.tvStorageUsedSpace) - internal val tvStorageFreeSpace = view.findViewById(R.id.tvStorageFreeSpace) - internal val btnShowGrantedUri = view.findViewById