()
+
+ /**
+ * Call this method to save beginning time by passed key
+ * @param key value to identify calculation for performance
+ */
+ fun start(key: String) =
+ performanceInfoMap.put(key, PerformanceInfo().apply { setStartTime(System.nanoTime()) })
+
+ /**
+ * Call this method to get calculated PerformanceInfo by passed key
+ * @param key value to identify calculation for performance
+ * @return calculate PerformanceInfo for passed key
+ */
+ fun stop(key: String): PerformanceInfo? =
+ performanceInfoMap.remove(key)?.apply { setEndTime(System.nanoTime()) }
+
+ class PerformanceInfo {
+
+ private var startTime: Long = 0
+ private var endTime: Long = 0
+
+ internal fun setStartTime(startTime: Long) {
+ this.startTime = startTime
+ }
+
+ internal fun setEndTime(endTime: Long) {
+ this.endTime = endTime
+ }
+
+ override fun toString(): String {
+ if (startTime <= 0) {
+ return "Not Started"
+ }
+ if (endTime <= 0) {
+ return "In Progress"
+ }
+ val mcs = TimeUnit.MICROSECONDS.convert(endTime - startTime, TimeUnit.NANOSECONDS).toFloat()
+ if (mcs >= MICROSECONDS_IN_MILLISECOND) {
+ var ms = TimeUnit.MILLISECONDS.convert(endTime - startTime, TimeUnit.NANOSECONDS).toFloat()
+ if (ms >= MILLISECONDS_IN_SECOND) {
+ var s = TimeUnit.SECONDS.convert(endTime - startTime, TimeUnit.NANOSECONDS).toFloat()
+ s += (ms - s * MILLISECONDS_IN_SECOND) / MILLISECONDS_IN_SECOND
+ return String.format(Locale.US, TIME_PATTERN, s)
+ }
+ ms += (mcs - ms * MICROSECONDS_IN_MILLISECOND) / MILLISECONDS_IN_SECOND
+ return String.format(Locale.US, TIME_PATTERN, ms)
+ }
+ return String.format(Locale.US, TIME_PATTERN, mcs / MICROSECONDS_IN_MILLISECOND)
+ }
+ }
+}
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileExt.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileExt.kt
new file mode 100644
index 0000000..212ec31
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileExt.kt
@@ -0,0 +1,18 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import androidx.core.content.FileProvider
+import java.io.File
+
+/**
+ * @param context instance of Context[Context]
+ * @param authority the authority of a [FileProvider] defined in a element in your app's manifest
+ *
+ * @return a content URI for the file
+ */
+internal fun File.getUri(context: Context,
+ authority: String): Uri =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) FileProvider.getUriForFile(context, authority, this)
+ else Uri.fromFile(this)
\ No newline at end of file
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileUtils.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileUtils.kt
new file mode 100644
index 0000000..d1b85da
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileUtils.kt
@@ -0,0 +1,215 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.net.Uri
+import android.os.Environment
+import android.provider.DocumentsContract
+import com.cleveroad.bootstrap.kotlin_core.utils.storage.UriUtils.Companion.isContentScheme
+import com.cleveroad.bootstrap.kotlin_core.utils.storage.UriUtils.Companion.isFileScheme
+import com.cleveroad.bootstrap.kotlin_core.utils.storage.UriUtils.Companion.isGoogleDrive
+import com.cleveroad.bootstrap.kotlin_core.utils.storage.UriUtils.Companion.isGooglePhotos
+import com.cleveroad.bootstrap.kotlin_ext.withNotNull
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.util.*
+
+/**
+ * Provides common methods of the utility for working with file
+ */
+class FileUtils private constructor() {
+
+ companion object {
+
+ /**
+ * Get file's path by uri
+ *
+ * @param context instance of Context[Context]
+ * @param fileUri file's uri[Uri]
+ *
+ * @return file's path
+ */
+ @SuppressLint("NewApi")
+ fun getRealPath(context: Context, fileUri: Uri?): String? =
+ fileUri?.let { uri ->
+ when {
+ // DocumentProvider
+ DocumentsContract.isDocumentUri(context, uri) -> when (uri.authority) {
+ EXTERNAL_STORAGE_PROVIDER -> getExternalStoragePath(uri)
+ DOWNLOADS_PROVIDER -> getDownloadsPath(context, uri)
+ MEDIA_PROVIDER -> getMediaPath(context, uri)
+ else -> getFileName(context, uri)
+ }
+ isContentScheme(uri) -> when {
+ isGooglePhotos(uri) -> uri.lastPathSegment
+ isGoogleDrive(uri) -> copyFile(context, uri, TypeDirPath.CACHE)?.absolutePath
+ else -> getDataColumn(context, uri)
+ ?: copyFile(context, uri, TypeDirPath.CACHE)?.absolutePath
+ }
+ isFileScheme(uri) -> uri.path
+ else -> getDefaultPath(context, uri)
+ }
+ }
+
+ /**
+ * Method copy file from Uri to custom path and get new file's path
+ *
+ * @param context instance of Context[Context]
+ * @param fileUri file's uri[Uri]
+ * @param pathTo copy path. Default = External public storage with [Environment.DIRECTORY_PICTURES] file name
+ *
+ * @return file's path
+ */
+ @SuppressLint("NewApi")
+ fun copyFileAndGetRealPath(context: Context,
+ fileUri: Uri?,
+ pathTo: String? = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)?.absolutePath): String? =
+ fileUri?.run { copyFile(context, this, pathTo)?.absolutePath }
+
+ /**
+ * Method create new file
+ * @param path file path. Default = External storage with [TEMP_FILES_DIR] file name
+ * @param deleteIfExist delete file if it already created in path. Default = true
+ * @return [File] created file. May return null if path is null
+ */
+ fun createFile(path: String?,
+ title: String? = null,
+ deleteIfExist: Boolean = true): File? =
+ path?.run {
+ File(this, title ?: UUID.randomUUID().toString()).apply {
+ if (deleteIfExist && exists()) {
+ delete()
+ createNewFile()
+ }
+ }
+ }
+
+ /**
+ * Method create new file
+ * @param context [Context]Application context
+ * @param dirType [TypeDirPath] type of directory
+ * @param deleteIfExist [Boolean] delete file if it already created in path. Default = true
+ * @return [File] created file. May return null if storage is not currently available.
+ */
+ fun createFile(context: Context,
+ dirType: TypeDirPath = TypeDirPath.CACHE,
+ title: String? = UUID.randomUUID().toString(),
+ deleteIfExist: Boolean = true): File? =
+ createFile(dirType.path(context), title, deleteIfExist)
+
+ /**
+ * Method copy file from Uri to custom path
+ * @param context Application context
+ * @param fileFrom file uri which should be copied
+ * @param pathTo copy path. Default = External storage with [TEMP_FILES_DIR] file name
+ * @param deleteIfExist delete file if it already created in path. Default = true
+ */
+ @Throws(Exception::class)
+ fun copyFile(context: Context, fileFrom: Uri,
+ pathTo: String? = context.cacheDir.absolutePath,
+ deleteIfExist: Boolean = true): File? {
+ context.contentResolver.openInputStream(fileFrom)?.let { inputStream ->
+ BufferedInputStream(inputStream).use { innerInputStream ->
+ withNotNull(innerInputStream) {
+ val originalSize = available()
+ val fileName = getFileName(context, fileFrom)
+ val file = createFile(pathTo, fileName, deleteIfExist)
+ file?.let {
+ BufferedOutputStream(FileOutputStream(it, false)).use { outputStream ->
+ val buf = ByteArray(originalSize)
+ while (read(buf) != -1) {
+ outputStream.write(buf)
+ }
+ outputStream.flush()
+ }
+ }
+ return file
+ }
+ }
+ }
+ return null
+ }
+
+ /**
+ * Method copy file from Uri to custom path
+ * @param context Application context
+ * @param fileFrom file uri which should be copied
+ * @param dirType [TypeDirPath.CACHE] = context.cacheDir,
+ * [TypeDirPath.EXTERNAL_TEMP] = context.getExternalFilesDir([TEMP_FILES_DIR])
+ * @param deleteIfExist delete file if it already created in path. Default = true
+ */
+ @Throws(Exception::class)
+ fun copyFile(context: Context, fileFrom: Uri,
+ dirType: TypeDirPath = TypeDirPath.CACHE,
+ deleteIfExist: Boolean = true): File? =
+ copyFile(context, fileFrom, dirType.path(context), deleteIfExist)
+
+
+ /**
+ * Create directory in the cache path [Context.getCacheDir]
+ * @param dirPath [String] directory path
+ * @param dirName [String] new directory name, by default it will be [UUID.randomUUID]
+ * @param clearIfExist [Boolean] true: if directory has already created, it will be deleted
+ */
+ fun createDir(dirPath: String?,
+ dirName: String? = null,
+ clearIfExist: Boolean = true) =
+ File(dirPath, dirName ?: UUID.randomUUID().toString()).apply {
+ if (clearIfExist) deleteRecursively()
+ if (!exists()) mkdir()
+ }
+
+ /**
+ * Create directory in the cache path [Context.getCacheDir]
+ * @param context [Context] instance of Context
+ * @param dirPath [String] directory path
+ * @param dirName [String] new directory name, by default it will be [UUID.randomUUID]
+ * @param clearIfExist [Boolean] true: if directory has already created, it will be deleted
+ */
+ fun createDir(context: Context,
+ dirPath: TypeDirPath = TypeDirPath.CACHE,
+ dirName: String? = UUID.randomUUID().toString(),
+ clearIfExist: Boolean = true) =
+ createDir(dirPath.path(context), dirName, clearIfExist)
+
+ /**
+ * Deletes the file or directory denoted by this abstract pathname. If
+ * this pathname denotes a directory, then the directory will be deleted with all its children.
+ *
+ * Note that the {@link java.nio.file.Files} class defines the [java.nio.file.Files.delete]
+ * method to throw an [java.io.IOException]
+ * when a file cannot be deleted. This is useful for error reporting and to
+ * diagnose why a file cannot be deleted.
+ *
+ * @return true if and only if the file or directory is
+ * successfully deleted; false otherwise
+ *
+ * @throws SecurityException
+ * If a security manager exists and its [java.lang.SecurityManager.checkDelete] method denies
+ * delete access to the file
+ */
+ fun delete(file: File): Boolean =
+ file.takeIf { it.exists() }
+ ?.run { if (isDirectory) deleteRecursively() else delete() }
+ ?: false
+
+ /**
+ * See [delete]
+ */
+ fun delete(path: String): Boolean = delete(File(path))
+
+ /**
+ * Gets the extension of a file.
+ * @param file
+ * @return extension including the dot, or empty if there is no extension.
+ */
+ fun getExtension(file: File): String? =
+ file.takeIf { it.exists() }?.extension
+ ?: file.absolutePath
+ .lastIndexOf(DOT_CHAR)
+ .takeIf { it >= 0 }
+ ?.let { file.absolutePath.substring(it) }
+ }
+}
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileUtilsCommon.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileUtilsCommon.kt
new file mode 100644
index 0000000..4692eb1
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/FileUtilsCommon.kt
@@ -0,0 +1,171 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+import android.annotation.TargetApi
+import android.content.ContentUris
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.provider.OpenableColumns
+import androidx.annotation.NonNull
+import androidx.annotation.RequiresApi
+import com.cleveroad.bootstrap.kotlin_core.utils.storage.TypeMediaFile.*
+import com.cleveroad.bootstrap.kotlin_core.utils.storage.UriUtils.Companion.isContentScheme
+import java.io.File
+
+/**
+ * Get external storage provider path by uri[Uri]
+ *
+ * @param uri file's uri[Uri]
+ *
+ * @return external storage documents path
+ */
+@RequiresApi(Build.VERSION_CODES.KITKAT)
+internal fun getExternalStoragePath(uri: Uri): String? {
+ return separateColumnsOfDocument(DocumentsContract.getDocumentId(uri))
+ .takeIf { PRIMARY.equals(it[NUMBER_COLUMN_TYPE_DOCUMENT], ignoreCase = true) }
+ ?.let { "${Environment.getExternalStorageDirectory()}/${it[NUMBER_COLUMN_PATH_DOCUMENT]}" }
+}
+
+/**
+ * Get Uri path from Downloads Provider.
+ * @param context of the caller.
+ * @param uri to parse.
+ * @return uri path.
+ */
+@TargetApi(Build.VERSION_CODES.KITKAT)
+internal fun getDownloadsPath(context: Context, uri: Uri): String? =
+ getDocumentId(uri)?.let { docId ->
+ if (docId.startsWith(RAW_DATA)) {
+ return docId.replaceFirst(RAW_DATA.toRegex(), EMPTY_STRING_VALUE)
+ }
+ return try {
+ val contentUri = ContentUris.withAppendedId(Uri.parse(PUBLIC_DOWNLOADS_PATH), java.lang.Long.valueOf(docId))
+ val filePath = getDefaultPath(context, contentUri)?.takeIf { it != docId }
+ val filePathAlternative = getDisplayName(context, uri, Environment.DIRECTORY_DOWNLOADS)
+ when {
+ filePath != null && File(filePath).exists() -> filePath
+ filePathAlternative != null && File(filePathAlternative).exists() -> filePathAlternative
+ else -> null
+ }
+ } catch (e: NumberFormatException) {
+ uri.toString()
+ }
+ }
+
+/**
+ * Get Uri path from Media Provider.
+ * @param context of the caller.
+ * @param uri to parse.
+ * @return uri path.
+ */
+@TargetApi(Build.VERSION_CODES.KITKAT)
+internal fun getMediaPath(@NonNull context: Context, @NonNull uri: Uri): String? =
+ getColumnsByDocumentId(uri)?.let { split ->
+ val contentUri: Uri
+ val column: String
+ val selection: String
+ when (TypeMediaFile.byValue(split[NUMBER_COLUMN_TYPE_DOCUMENT].toLowerCase())) {
+ IMAGE -> {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ column = MediaStore.Images.Media.DATA
+ selection = MediaStore.Images.Media._ID + ID_SUFFIX
+ }
+ VIDEO -> {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ column = MediaStore.Video.Media.DATA
+ selection = MediaStore.Video.Media._ID + ID_SUFFIX
+ }
+ AUDIO -> {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+ column = MediaStore.Audio.Media.DATA
+ selection = MediaStore.Audio.Media._ID + ID_SUFFIX
+ }
+ else -> {
+ contentUri = MediaStore.Files.getContentUri(EXTERNAL_DATA)
+ column = COLUMN_DATA
+ selection = ID_FULL_SUFFIX
+ }
+ }
+ getDataColumn(context, contentUri, column, selection, arrayOf(split[NUMBER_COLUMN_PATH_DOCUMENT]))
+ }
+
+/**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param context instance of Context[Context]
+ * @param uri The Uri to query.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ *
+ * @return The value of the [projection] column, which is typically a file path.
+ */
+internal fun getDataColumn(context: Context,
+ uri: Uri,
+ projection: String = MediaStore.MediaColumns.DATA,
+ selection: String? = null,
+ selectionArgs: Array? = null): String? =
+ try {
+ context
+ .contentResolver
+ .query(uri, arrayOf(projection), selection, selectionArgs, null)
+ .use { cursor ->
+ cursor?.takeIf { it.moveToFirst() }
+ ?.getString(cursor.getColumnIndexOrThrow(projection))
+ }
+ } catch (exc: Throwable) {
+ null
+ }
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+internal fun getDocumentId(uri: Uri): String? =
+ DocumentsContract.getDocumentId(uri).takeIf { it.isNotEmpty() }
+
+/**
+ * Get Uri path from content resolver.
+ * @param context of the caller.
+ * @param uri to parse.ё
+ * @return uri path.
+ */
+internal fun getDefaultPath(context: Context, uri: Uri): String? =
+ getDataColumn(context, uri)?.takeUnless { it.isEmpty() }
+ ?: getFileName(context, uri)
+
+/**
+ * Get Uri path from downloads directory with display name.
+ * @param context of the caller.
+ * @param uri to parse.
+ * @param environment [Environment.DIRECTORY_DOWNLOADS] by default
+ * @return uri path.
+ */
+internal fun getDisplayName(context: Context, uri: Uri, environment: String = Environment.DIRECTORY_DOWNLOADS): String? =
+ getDataColumn(context, uri, OpenableColumns.DISPLAY_NAME).let { name ->
+ Environment
+ .getExternalStoragePublicDirectory(environment)
+ .absolutePath.plus(File.separator)
+ .plus(name)
+ .takeIf { File(it).exists() }
+ }
+
+internal fun separateColumnsOfDocument(document: String) =
+ document.split(REGEX_FOR_SEPARATING_COLUMNS_OF_DOCUMENT.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+
+internal fun getColumnsByDocumentId(uri: Uri): Array? =
+ getDocumentId(uri)?.let { documentId ->
+ separateColumnsOfDocument(documentId).takeUnless { it.size < MIN_COLUMNS_TO_SEPARATE }
+ }
+
+/**
+ * Get file name.
+ * @param context of the caller.
+ * @param uri to parse.
+ * @return file name or null if unknown.
+ */
+internal fun getFileName(context: Context, uri: Uri): String? {
+ var fileName: String? = null
+ if (isContentScheme(uri)) fileName = getDataColumn(context, uri, OpenableColumns.DISPLAY_NAME)
+ return fileName ?: uri.lastPathSegment
+}
\ No newline at end of file
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/ImageUtils.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/ImageUtils.kt
new file mode 100644
index 0000000..f90b29c
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/ImageUtils.kt
@@ -0,0 +1,313 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+
+import android.content.ContentValues
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Matrix
+import android.net.Uri
+import android.provider.MediaStore
+import android.util.Log
+import androidx.exifinterface.media.ExifInterface
+import com.cleveroad.bootstrap.kotlin_core.utils.storage.TypeDirPath.CACHE
+import java.io.File
+import java.io.FileOutputStream
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.math.max
+
+/**
+ * Provides common methods of the utility for working with image
+ */
+class ImageUtils private constructor() {
+
+ companion object {
+
+ /**
+ * The image is compressed if its size is greater than [maxSize]
+ * or its resolution is greater than [maxResolution]. Maximum resolution of the image by default [MAX_IMAGE_RESOLUTION] and
+ * maximum size of the image by default [MAX_IMAGE_SIZE].
+ *
+ * @param imageUri image's uri[Uri]
+ * @param maxResolution maximum resolution image
+ * @param maxSize maximum size image
+ *
+ * @return image file[File]
+ */
+ fun compressImage(imageUri: Uri,
+ maxResolution: Int = MAX_IMAGE_RESOLUTION,
+ maxSize: Double = MAX_IMAGE_SIZE): Bitmap? =
+ File(imageUri.path).takeIf { it.exists() }?.let { file ->
+ with(getImageSize(imageUri)) {
+ when {
+ file.length() > maxSize -> {
+ (file.length() / maxSize).let { reductionCoef ->
+ decodeBitmap(file, outWidth.toDouble() / reductionCoef,
+ outHeight.toDouble() / reductionCoef).run {
+ max(width, height).takeIf { it > maxResolution }?.let {
+ with(max(width, height).toDouble() / maxResolution) {
+ resizeBitmap(this@run, width / this, height / this)
+ }
+ } ?: this@run
+ }
+ }
+ }
+ max(outWidth, outHeight) > maxResolution -> {
+ with(max(outWidth, outHeight).toDouble() / maxResolution) {
+ decodeBitmap(file, outWidth / this, outHeight / this)
+ }
+ }
+ else -> BitmapFactory.decodeFile(file.path)
+ }
+ }
+ }
+
+ /**
+ * Save bitmap[Bitmap] to file[File]
+ *
+ * @param destinationFile destination file[File]
+ * @param bitmap bitmap[Bitmap]
+ * @param quality compress quality by default [COMPRESS_QUALITY]
+ * @param recycle recycle Bitmap in the end. Default = true
+ *
+ * @return returns true if the file is saved successfully otherwise false
+ */
+ fun saveBitmap(destinationFile: File?,
+ bitmap: Bitmap?,
+ quality: Int = COMPRESS_QUALITY,
+ recycle: Boolean = true): Boolean {
+ try {
+ FileOutputStream(destinationFile).use { bitmap?.compress(Bitmap.CompressFormat.JPEG, quality, it) }
+ } catch (e: Exception) {
+ Log.e("Error", e.message)
+ return false
+ } finally {
+ if (recycle) bitmap?.recycle()
+ }
+ return true
+ }
+
+ /**
+ * Create temporary image file with extension .jpg
+ *
+ * @param path directory path
+ * @param datePattern date pattern by default[DATE_PATTERN]
+ *
+ * @return image file[File]. May return null if path is null
+ */
+ fun createImageFile(path: String?,
+ fileName: String?,
+ datePattern: String = DATE_PATTERN): File? =
+ path?.run {
+ val timeStamp = fileName
+ ?: SimpleDateFormat(datePattern, Locale.getDefault()).format(Date())
+ val dir = FileUtils.createDir(path, clearIfExist = false)
+
+ File.createTempFile(timeStamp, IMAGE_FORMAT_JPG, dir)
+ }
+
+
+ /**
+ * Create temporary image file with extension .jpg
+ *
+ * @param context instance of Context[Context]
+ * @param dirType type of directory [TypeDirPath]
+ * @param datePattern date pattern by default[DATE_PATTERN]
+ *
+ * @return image file[File]. May return null if path is null
+ */
+ fun createImageFile(context: Context,
+ dirType: TypeDirPath = CACHE,
+ fileName: String?,
+ datePattern: String = DATE_PATTERN): File? =
+ createImageFile(dirType.path(context), fileName, datePattern)
+
+ /**
+ * Get bitmap option[BitmapFactory.Options]
+ *
+ * @param path image file path
+ *
+ * @return bitmap option[BitmapFactory.Options]
+ */
+ fun getImageSize(path: String) = BitmapFactory.Options().apply {
+ inJustDecodeBounds = true
+ BitmapFactory.decodeFile(path, this)
+ }
+
+ /**
+ * Get bitmap option[BitmapFactory.Options]
+ *
+ * @param file image file[File]
+ *
+ * @return bitmap option[BitmapFactory.Options]
+ */
+ fun getImageSize(file: File) = getImageSize(file.absolutePath)
+
+ /**
+ * Get bitmap option[BitmapFactory.Options] by image uri[Uri]
+ *
+ * @param uri image uri[Uri]
+ *
+ * @return bitmap option[BitmapFactory.Options]
+ */
+ fun getImageSize(uri: Uri) = getImageSize(File(uri.path))
+
+ /**
+ * Decode image from file path for resolution
+ *
+ * @param path image file path
+ * @param targetWidth width image
+ * @param targetHeight height image
+ *
+ * @return bitmap option[Bitmap]
+ */
+ private fun decodeBitmap(path: String,
+ targetWidth: Double,
+ targetHeight: Double): Bitmap =
+ with(BitmapFactory.Options()) {
+ inJustDecodeBounds = true
+ BitmapFactory.decodeFile(path, this)
+
+ // Calculate inSampleSize
+ inSampleSize = calculateInSampleSize(this, targetWidth, targetHeight)
+
+ // Decode bitmap with inSampleSize set
+ inJustDecodeBounds = false
+ inPreferredConfig = Bitmap.Config.ARGB_8888
+ BitmapFactory.decodeFile(path, this)
+ }
+
+ /**
+ * Decode image from file [File] for resolution
+ *
+ * @param file image file [File]
+ * @param targetWidth width image
+ * @param targetHeight height image
+ *
+ * @return bitmap option[Bitmap]
+ */
+ private fun decodeBitmap(file: File,
+ targetWidth: Double,
+ targetHeight: Double): Bitmap =
+ decodeBitmap(file.absolutePath, targetWidth, targetHeight)
+
+ /**
+ * Find scale[BitmapFactory.Options.inSampleSize] for the resolution
+ *
+ * @param options bitmap option[BitmapFactory.Options]
+ * @param targetWidth width image
+ * @param targetHeight height image
+ *
+ * @return scale[BitmapFactory.Options.inSampleSize]
+ */
+ private fun calculateInSampleSize(options: BitmapFactory.Options,
+ targetWidth: Double,
+ targetHeight: Double): Int {
+ // Raw height and width of image
+ val height = options.outHeight
+ val width = options.outWidth
+ var inSampleSize = IN_SAMPLE_SIZE
+ if (height > targetHeight || width > targetWidth) {
+ do {
+ inSampleSize *= RESOLUTION_REDUCTION_COEFFICIENT
+ } while (inSampleSize != 0 && (height / inSampleSize >= targetHeight && width / inSampleSize >= targetWidth))
+ }
+
+ return inSampleSize
+ }
+
+ /**
+ * Get image's uri[Uri] from file
+ *
+ * @param context instance of Context[Context]
+ * @param imageFile image's file[File]
+ *
+ * @return image's uri[Uri]
+ */
+ fun getImageUri(context: Context,
+ imageFile: File): Uri? =
+ context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Images.Media._ID),
+ "${MediaStore.Images.Media.DATA}=? ", arrayOf(imageFile.absolutePath), null).use {
+ when {
+ it?.moveToFirst() == true -> {
+ val id = it.getInt(it.getColumnIndex(MediaStore.MediaColumns._ID))
+ Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
+ }
+ imageFile.exists() -> {
+ val values = ContentValues()
+ values.put(MediaStore.Images.Media.DATA, imageFile.absolutePath)
+ context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
+ }
+ else -> null
+ }
+ }
+
+ /**
+ * Modify bitmap size.
+ * @return Returned new resized bitmap
+ */
+ fun resizeBitmap(bitmap: Bitmap,
+ newWidth: Double,
+ newHeight: Double): Bitmap {
+ val width = bitmap.width
+ val height = bitmap.height
+ val scaleWidth = newWidth.toFloat() / width
+ val scaleHeight = newHeight.toFloat() / height
+ // Create a matrix for manipulation
+ val matrix = Matrix()
+ // Resize the bitmap
+ matrix.postScale(scaleWidth, scaleHeight)
+ // Create bitmap with new parameters
+ val resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false)
+ bitmap.recycle()
+ return resizedBitmap
+ }
+
+ /**
+ * Modify orientation image to normal orientation [ExifInterface.ORIENTATION_NORMAL]
+ *
Used [rotate] method
+ * @param bitmap bitmap[Bitmap]
+ * @param path absolute file path
+ *
+ * @return new bitmap [Bitmap] with normal orientation [ExifInterface.ORIENTATION_NORMAL]
+ */
+ fun rotateToNormal(bitmap: Bitmap,
+ path: String): Bitmap {
+ val ei = ExifInterface(path)
+ val orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.ORIENTATION_NORMAL)
+ return when (orientation) {
+ ExifInterface.ORIENTATION_ROTATE_90 -> rotate(bitmap, 90F)
+ ExifInterface.ORIENTATION_ROTATE_180 -> rotate(bitmap, 180F)
+ ExifInterface.ORIENTATION_ROTATE_270 -> rotate(bitmap, 270F)
+ else -> bitmap
+ }
+ }
+
+ /**
+ * Rotates bitmap to a specific degree
+ *
+ * @param bitmap bitmap[Bitmap]
+ * @param degree degree
+ * @param mirror use mirror effect flag
+ * @param recycle recycle old bitmap flag
+ *
+ * @return new rotated bitmap [Bitmap]
+ */
+ fun rotate(bitmap: Bitmap,
+ degree: Float,
+ mirror: Boolean = false,
+ recycle: Boolean = false): Bitmap = with(Matrix()) {
+ if (mirror) preScale(1F, -1F)
+ postRotate(degree)
+ Bitmap.createBitmap(bitmap,
+ 0,
+ 0,
+ bitmap.width,
+ bitmap.height,
+ this,
+ true).apply { if (recycle) bitmap.recycle() }
+ }
+ }
+}
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/StorageContract.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/StorageContract.kt
new file mode 100644
index 0000000..51695ab
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/StorageContract.kt
@@ -0,0 +1,43 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+internal const val PRIMARY = "primary"
+internal const val SELECTION_BY_DOCUMENT_ID = "_id=?"
+internal const val CONTENT = "content"
+internal const val FILE = "file"
+internal const val COLUMN_DATA = "_data"
+internal const val REGEX_FOR_SEPARATING_COLUMNS_OF_DOCUMENT = ":"
+internal const val NUMBER_COLUMN_TYPE_DOCUMENT = 0
+internal const val NUMBER_COLUMN_PATH_DOCUMENT = 1
+
+/** Providers from uri. */
+internal const val EXTERNAL_STORAGE_PROVIDER = "com.android.externalstorage.documents"
+internal const val DOWNLOADS_PROVIDER = "com.android.providers.downloads.documents"
+internal const val MEDIA_PROVIDER = "com.android.providers.media.documents"
+internal const val GOOGLE_PHOTOS_PROVIDER = "com.google.android.apps.photos.content"
+internal const val GOOGLE_DRIVE_PROVIDER = "com.google.android.apps.docs.storage.legacy"
+/** Content. */
+internal const val PUBLIC_DOWNLOADS_PATH = "content://downloads/public_downloads"
+/** Uri scheme. */
+internal const val CONTENT_SCHEME = "content"
+internal const val FILE_SCHEME = "file"
+private const val FILE_PROVIDER = ".provider"
+internal const val EXTERNAL_DATA = "external"
+internal const val RAW_DATA = "raw:"
+/** Common */
+internal const val DOT_CHAR = "."
+internal const val ID_SUFFIX = "=?"
+internal const val EMPTY_STRING_VALUE = ""
+internal const val ID_FULL_SUFFIX = "_id=?"
+internal const val MIN_COLUMNS_TO_SEPARATE = 2
+internal const val TEMP_FILES_DIR = "temp_files"
+
+/** Images */
+internal const val TYPE_FILE_IMAGE = "image/*"
+internal const val MAX_IMAGE_SIZE = 1024.0 * 1024.0
+internal const val MAX_IMAGE_RESOLUTION = 1024
+internal const val COMPRESS_QUALITY = 90
+internal const val DATE_PATTERN = "yyyyMMdd_HHmmss"
+internal const val RESOLUTION_REDUCTION_COEFFICIENT = 2
+internal const val FLAG_NOT_MODIFY_DATA_RETURN = 0
+internal const val IN_SAMPLE_SIZE = 1
+internal const val IMAGE_FORMAT_JPG = ".jpg"
\ No newline at end of file
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/TypeDirPath.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/TypeDirPath.kt
new file mode 100644
index 0000000..29ae550
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/TypeDirPath.kt
@@ -0,0 +1,15 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+import android.content.Context
+
+enum class TypeDirPath {
+ CACHE,
+ EXTERNAL_TEMP;
+
+ fun path(context: Context): String? =
+ when (this) {
+ CACHE -> context.cacheDir.absolutePath
+ EXTERNAL_TEMP -> context.getExternalFilesDir(TEMP_FILES_DIR)?.absolutePath
+ }
+
+}
\ No newline at end of file
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/TypeMediaFile.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/TypeMediaFile.kt
new file mode 100644
index 0000000..99932fc
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/TypeMediaFile.kt
@@ -0,0 +1,13 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+enum class TypeMediaFile(val value: String) {
+ IMAGE("image"),
+ VIDEO("video"),
+ AUDIO("audio"),
+ UNKNOWN("UNKNOWN");
+
+ companion object {
+ fun byValue(value: String?) =
+ values().firstOrNull { value == it.value } ?: UNKNOWN
+ }
+}
\ No newline at end of file
diff --git a/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/UriUtils.kt b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/UriUtils.kt
new file mode 100644
index 0000000..b650069
--- /dev/null
+++ b/kotlin-core/src/main/kotlin/com/cleveroad/bootstrap/kotlin_core/utils/storage/UriUtils.kt
@@ -0,0 +1,59 @@
+package com.cleveroad.bootstrap.kotlin_core.utils.storage
+
+import android.net.Uri
+
+class UriUtils private constructor() {
+
+ companion object {
+
+ /**
+ * Checks whether the Uri authority is External Storage Provider.
+ * @param uri to check.
+ * @return true if it is, false otherwise.
+ */
+ fun isExternalStorage(uri: Uri) = EXTERNAL_STORAGE_PROVIDER.equals(uri.authority, ignoreCase = true)
+
+ /**
+ * Checks whether the Uri authority is Downloads Provider.
+ * @param uri to check.
+ * @return true if it is, false otherwise.
+ */
+ fun isDownloads(uri: Uri) = DOWNLOADS_PROVIDER.equals(uri.authority, ignoreCase = true)
+
+ /**
+ * Checks whether the Uri authority is Media Provider.
+ * @param uri to check.
+ * @return true if it is, false otherwise.
+ */
+ fun isMedia(uri: Uri) = MEDIA_PROVIDER.equals(uri.authority, ignoreCase = true)
+
+ /**
+ * Checks whether the Uri authority is Google Photos.
+ * @param uri to check.
+ * @return true if it is, false otherwise.
+ */
+ fun isGooglePhotos(uri: Uri) = GOOGLE_PHOTOS_PROVIDER.equals(uri.authority, ignoreCase = true)
+
+ /**
+ * Checks whether the Uri authority is Google Drive.
+ * @param uri to check.
+ * @return true if it is, false otherwise.
+ */
+ fun isGoogleDrive(uri: Uri) = GOOGLE_DRIVE_PROVIDER.equals(uri.authority, ignoreCase = true)
+
+ /**
+ * Checks whether the Uri scheme is Content.
+ * @param uri to check.
+ * @return true if it is, false otherwise.
+ */
+ fun isContentScheme(uri: Uri) = CONTENT_SCHEME.equals(uri.scheme, ignoreCase = true)
+
+ /**
+ * Checks whether the Uri scheme is File.
+ * @param uri to check.
+ * @return true if it is, false otherwise.
+ */
+ fun isFileScheme(uri: Uri) = FILE_SCHEME.equals(uri.scheme, ignoreCase = true)
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-core/src/main/res/anim/enter_from_left.xml b/kotlin-core/src/main/res/anim/enter_from_left.xml
new file mode 100644
index 0000000..c8af091
--- /dev/null
+++ b/kotlin-core/src/main/res/anim/enter_from_left.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-core/src/main/res/anim/enter_from_right.xml b/kotlin-core/src/main/res/anim/enter_from_right.xml
new file mode 100644
index 0000000..fa86a92
--- /dev/null
+++ b/kotlin-core/src/main/res/anim/enter_from_right.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-core/src/main/res/anim/exit_to_left.xml b/kotlin-core/src/main/res/anim/exit_to_left.xml
new file mode 100644
index 0000000..8bc78f7
--- /dev/null
+++ b/kotlin-core/src/main/res/anim/exit_to_left.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-core/src/main/res/anim/exit_to_right.xml b/kotlin-core/src/main/res/anim/exit_to_right.xml
new file mode 100644
index 0000000..d283f64
--- /dev/null
+++ b/kotlin-core/src/main/res/anim/exit_to_right.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-core/src/main/res/drawable/divider_item.xml b/kotlin-core/src/main/res/drawable/divider_item.xml
new file mode 100644
index 0000000..533ebae
--- /dev/null
+++ b/kotlin-core/src/main/res/drawable/divider_item.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-core/src/main/res/values/ids.xml b/kotlin-core/src/main/res/values/ids.xml
new file mode 100644
index 0000000..4bb9cb5
--- /dev/null
+++ b/kotlin-core/src/main/res/values/ids.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-core/src/main/res/values/strings.xml b/kotlin-core/src/main/res/values/strings.xml
new file mode 100644
index 0000000..073f870
--- /dev/null
+++ b/kotlin-core/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+ Core
+ Ok
+ No
+
+ Something went wrong
+
diff --git a/kotlin-core/src/main/res/values/theme_colors.xml b/kotlin-core/src/main/res/values/theme_colors.xml
new file mode 100644
index 0000000..f9822ea
--- /dev/null
+++ b/kotlin-core/src/main/res/values/theme_colors.xml
@@ -0,0 +1,10 @@
+
+
+ @android:color/black
+ @android:color/black
+ #ff01a3df
+ #ff0182b4
+ #ff00a5e2
+ #ffe3e3e3
+ #ffb6b6b6
+
\ No newline at end of file
diff --git a/kotlin-core/src/test/java/com/cleveroad/bootstrap/kotlin_core/ExampleUnitTest.java b/kotlin-core/src/test/java/com/cleveroad/bootstrap/kotlin_core/ExampleUnitTest.java
new file mode 100644
index 0000000..64cece1
--- /dev/null
+++ b/kotlin-core/src/test/java/com/cleveroad/bootstrap/kotlin_core/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.cleveroad.bootstrap.kotlin_core;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/kotlin-ext/.gitignore b/kotlin-ext/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/kotlin-ext/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/kotlin-ext/README.md b/kotlin-ext/README.md
new file mode 100644
index 0000000..a5b2c5e
--- /dev/null
+++ b/kotlin-ext/README.md
@@ -0,0 +1,170 @@
+# Kotlin Ext [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome)
+
+## Meet Kotlin Ext by Cleveroad
+
+Collection of most used Kotlin extensions.
+
+### Description ###
+
+List of Most commonly used Kotlin Extensions
+
+- Activity
+- Assertion
+- Bitmap
+- Common
+- Context
+- EditText
+- File
+- ImageView
+- Keyboard
+- Screen (refer to Context)
+- String
+- TextView
+- Uri
+- View
+
+## Setup and usage
+### Installation
+by gradle :
+```groovy
+dependencies {
+ implementation 'com.cleveroad.bootstrap:kotlin-ext:2.0.0'
+}
+```
+### Usage ###
+#### Activity ####
+```groovy
+fun Activity.setAdjustNothing(): Unit //To set windowSoftInputMode SOFT_INPUT_ADJUST_NOTHING at runtime
+fun Activity.setAdjustResize(): Unit //To set windowSoftInputMode SOFT_INPUT_ADJUST_RESIZE at runtime
+fun Activity.setAdjustPan(): Unit //To set windowSoftInputMode SOFT_INPUT_ADJUST_PAN at runtime
+fun Activity.setFullScreen(): Unit //To hide all screen decorations (such as the status bar) while this window is displayed
+fun Activity.setRotatable(): Unit //To set requestedOrientation SCREEN_ORIENTATION_SENSOR at runtime
+fun Activity.setOnlyPortraitMode(): Unit //To set requestedOrientation SCREEN_ORIENTATION_PORTRAIT at runtime
+fun Activity.setOnlyLandscapeMode(): Unit //To set requestedOrientation SCREEN_ORIENTATION_LANDSCAPE at runtime
+```
+#### Assertion ####
+```groovy
+fun Any?.assertNotNull(parameterName: String = ""): Any //throw AssertionError if parameter is null
+fun Boolean.assertTrue(message: String): Boolean //throw AssertionError if boolean value is not true
+fun Boolean.assertFalse(message: String): Boolean //throw AssertionError if boolean value is not false
+fun Any?.assertNotEquals(anotherValue: Any, parameterName: String = "parameter"): Boolean //throw AssertionError if value is not equals another value
+fun String?.assertNotEmpty(parameterName: String): Boolean //throw AssertionError if string is null or empty
+fun Any?.assertInstanceOf(parameterName: String): Boolean //throw AssertionError is value is not belong to class T
+```
+#### Bitmap ####
+```groovy
+fun Bitmap?.getIfNotRecycled(): Bitmap? //return bitmap if it is recycled or null
+fun Bitmap?.checkAndRecycle(): Unit? //check if not null and recycle if not recycled
+```
+#### Common ####
+```groovy
+fun safeLet(p1: T1?, p2: T2?, block: (T1, T2) -> R?): R?
+fun safeLet(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3) -> R?): R?
+fun safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, block: (T1, T2, T3, T4) -> R?): R?
+fun safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, block: (T1, T2, T3, T4, T5) -> R?): R?
+fun safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, p6: T6?, block: (T1, T2, T3, T4, T5, T6) -> R?): R?
+//Calls the specified function [block] with `p1, p2...p6` not null values as its arguments and returns its result
+
+inline fun withNotNull(receiver: T?, block: T.() -> R): R? //Calls the specified function [block] with not null `this` value as its receiver and returns its result
+```
+#### Context ####
+```groovy
+fun Context.getFontResourcesCompat(@FontRes fontRes: Int): Typeface? //Returns a font Typeface associated with a particular resource ID or throws NotFoundException if the given ID does not exist
+fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? //Returns a drawable object associated with a particular resource ID or throws NotFoundException if the given ID does not exist
+fun Context.getColorCompat(@ColorRes id: Int): Int //Returns a color object associated with a particular resource ID or throws NotFoundException if the given ID does not exist
+fun Context.getInteger(@IntegerRes intRes: Int): Int //Returns a integer object associated with a particular resource ID or throws NotFoundException if the given ID does not exist
+fun Context.getStringArray(@ArrayRes id: Int): Array//Returns the string array object associated with a particular resource ID or throws NotFoundException if the given ID does not exist
+fun Context.isNetworkConnected(): Boolean //return true if network connectivity exists, false otherwise.
+```
+#### EditText ####
+```groovy
+fun EditText.getStringText(): String //Return the text the EditText is displaying as String.
+fun EditText.getTrimStringText(): String //Return the text the EditText is displaying as String and trailing whitespace removed.
+fun EditText.setOnDrawableClick(type: DrawablePositionTypes, callback: () -> Unit): Unit //handle touch on compoundDrawables
+```
+#### File ####
+```groovy
+fun File.getUri(context: Context, authority: String): Uri //return a content URI for the file
+```
+#### ImageView ####
+```groovy
+fun ImageView.getBitmap(): Bitmap? //Returns the bitmap used by view drawable to render
+```
+#### Keyboard ####
+```groovy
+fun Context.showKeyboard() //Extension method to provide show keyboard
+fun androidx.fragment.app.Fragment.showKeyboard() //Extension method to provide show keyboard for Activity androidx
+fun Activity.hideKeyboard() //Extension method to provide hide keyboard for Activity
+fun SupportFragment.hideKeyboard() //Extension method to provide hide keyboard for SupportFragment
+fun Fragment.hideKeyboard() //Extension method to provide hide keyboard for Fragment
+```
+#### Screen ####
+```groovy
+fun Context.dpToPx(dp: Float): Float //return Float calculated density independent pixels (DiP, DP) to device pixels
+fun Context.pxToDp(px: Float): Float //return Float calculated device pixels as density independent pixels (DiP, DP)
+fun Context.getScreenSize(): Point //Returns size of screen as Point
+fun Context.getScreenDisplayMetrics(): DisplayMetrics // Returns size of screen as DisplayMetrics
+```
+#### String ####
+```groovy
+fun String.toUri(): Uri //Creates a Uri which parses the given encoded URI string
+fun String.containsDigit(): Boolean //Returns true if contains a digit.
+fun String.containsSymbolOrNumber(): Boolean //Returns true if string matches the "(?=.*?[^a-zA-Z\\s]).+\$" regular expression.
+fun String.containsUpperCase(): Boolean //Returns true if contains character in upper case
+fun String.containsLowerCase(): Boolean //Returns true if contains character in lower case
+fun String?.fromBase64(): ByteArray? //Decode a string from Base64 to ByteArray
+fun String?.toBase64FromString(): String? //Encode a string to Base64
+fun String?.fromBase64ToString(): String? //Decode a string from Base64 to String
+```
+#### TextView ####
+```groovy
+fun TextView.getStringText(): String //Return the text the TextView is displaying
+fun TextView.getTrimStringText(): String //Return the trimmed text the TextView is displaying
+fun TextView.addFocusChangedListener(listener: (isHasFocus: Boolean) -> Unit) //Register a callback to be invoked when focus of this view changed
+fun TextView.setFont(fontId: Int) //Sets the typeface and style in which the text should be displayed
+fun TextView.setTextColorCompat(colorId: Int) //Sets the text color for all the states (normal, selected, focused) to be this color.
+```
+#### Uri ####
+```groovy
+fun Uri.checkRealFilePath(): String //Check whether "contentUri" is real file path (not content uri)
+```
+#### View ####
+```groovy
+fun View.OnClickListener.setClickListeners(vararg views: View) //Register a callback to be invoked when this views is clicked
+fun View.isVisible(): Boolean //Return true if view has visibility Visible
+fun View.hide(gone: Boolean = true) //if gone set visibility GONE else INVISIBLE
+fun View.show() //set view visibility VISIBLE
+```
+### Support ###
+If you have any questions, issues or propositions, please create a new issue in this repository.
+
+If you want to hire us, send an email to sales@cleveroad.com or fill the form on contact page
+
+Follow us:
+
+[![Awesome](/images/social/facebook.png)](https://www.facebook.com/cleveroadinc/) [![Awesome](/images/social/twitter.png)](https://twitter.com/cleveroadinc) [![Awesome](/images/social/google.png)](https://plus.google.com/+CleveroadInc) [![Awesome](/images/social/linkedin.png)](https://www.linkedin.com/company/cleveroad-inc-) [![Awesome](/images/social/youtube.png)](https://www.youtube.com/channel/UCFNHnq1sEtLiy0YCRHG2Vaw)
+
+
+### License ###
+* * *
+ The MIT License (MIT)
+
+ Copyright (c) 2016 Cleveroad Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/kotlin-ext/build.gradle b/kotlin-ext/build.gradle
new file mode 100644
index 0000000..6335aca
--- /dev/null
+++ b/kotlin-ext/build.gradle
@@ -0,0 +1,55 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply from: '../main_jcenter.settings.gradle'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion as Integer
+ buildToolsVersion rootProject.ext.buildToolsVersion as String
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion as Integer
+ targetSdkVersion rootProject.ext.compileSdkVersion as Integer
+ targetSdkVersion rootProject.ext.compileSdkVersion as Integer
+ versionCode libraryVersionCode
+ versionName libraryVersion
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ debug {
+ minifyEnabled false
+ debuggable true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ release {
+ minifyEnabled false
+ debuggable false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ // tests
+ testImplementation testsDependencies.junit
+ testImplementation testsDependencies.runner
+
+ // common
+ implementation kotlinDependencies.jdk
+ implementation supportDependencies.appCompat
+
+ // rx
+ implementation rxjava2Dependencies.rxkotlin
+ implementation rxjava2Dependencies.rxandroid
+
+}
+
+repositories {
+ mavenCentral()
+}
diff --git a/kotlin-ext/proguard-rules.pro b/kotlin-ext/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/kotlin-ext/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# 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
diff --git a/kotlin-ext/src/main/AndroidManifest.xml b/kotlin-ext/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d3ab96d
--- /dev/null
+++ b/kotlin-ext/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ActivityExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ActivityExt.kt
new file mode 100644
index 0000000..4d01461
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ActivityExt.kt
@@ -0,0 +1,47 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.app.Activity
+import android.content.pm.ActivityInfo
+import android.view.WindowManager
+
+/**
+ * To set windowSoftInputMode SOFT_INPUT_ADJUST_NOTHING at runtime
+ */
+fun Activity.setAdjustNothing() = window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
+
+/**
+ * To set windowSoftInputMode SOFT_INPUT_ADJUST_RESIZE at runtime
+ */
+fun Activity.setAdjustResize() = window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
+
+/**
+ * To set windowSoftInputMode SOFT_INPUT_ADJUST_PAN at runtime
+ */
+fun Activity.setAdjustPan() = window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
+
+/**
+ * To hide all screen decorations (such as the status bar) while this window is displayed
+ */
+fun Activity.setFullScreen() = window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN)
+
+/**
+ * To set requestedOrientation SCREEN_ORIENTATION_SENSOR at runtime
+ */
+fun Activity.setRotatable() {
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
+}
+
+/**
+ * To set requestedOrientation SCREEN_ORIENTATION_PORTRAIT at runtime
+ */
+fun Activity.setOnlyPortraitMode() {
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+}
+
+/**
+ * To set requestedOrientation SCREEN_ORIENTATION_LANDSCAPE at runtime
+ */
+fun Activity.setOnlyLandscapeMode() {
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+}
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/AnyExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/AnyExt.kt
new file mode 100644
index 0000000..35f7ab2
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/AnyExt.kt
@@ -0,0 +1,7 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+fun Any?.returnTrue() = true
+
+fun Any?.returnFalse() = false
+
+fun Any?.returnNull() = null
\ No newline at end of file
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/AssertionExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/AssertionExt.kt
new file mode 100644
index 0000000..50bf7a4
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/AssertionExt.kt
@@ -0,0 +1,41 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+/**
+ * throw AssertionError if parameter is null
+ */
+@Throws(AssertionError::class)
+fun Any?.assertNotNull(parameterName: String = "") = this
+ ?: throw AssertionError("$parameterName can't be null.")
+
+/**
+ * throw AssertionError if boolean value is not true
+ */
+@Throws(AssertionError::class)
+fun Boolean.assertTrue(message: String) = if (this) this else throw AssertionError(message)
+
+/**
+ * throw AssertionError if boolean value is not false
+ */
+@Throws(AssertionError::class)
+fun Boolean.assertFalse(message: String) = this.not().assertTrue(message)
+
+/**
+ * throw AssertionError if value is not equals another value
+ */
+@Throws(AssertionError::class)
+fun Any?.assertNotEquals(anotherValue: Any, parameterName: String = "parameter") =
+ (this == anotherValue).assertTrue("$parameterName can't be equal to $anotherValue.")
+
+/**
+ * throw AssertionError if string is null or empty
+ */
+@Throws(AssertionError::class)
+fun String?.assertNotEmpty(parameterName: String) =
+ this.isNullOrBlank().assertFalse("$parameterName can't be empty.")
+
+/**
+ * throw AssertionError is value is not belong to class T
+ */
+@Throws(AssertionError::class)
+inline fun Any?.assertInstanceOf(parameterName: String) =
+ (this is T).assertTrue(parameterName + " is not instance of " + T::class.java.name + ".")
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/BitmapExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/BitmapExt.kt
new file mode 100644
index 0000000..35dfa61
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/BitmapExt.kt
@@ -0,0 +1,13 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.graphics.Bitmap
+
+/**
+ * @return bitmap if it is recycled or null
+ */
+fun Bitmap?.getIfNotRecycled() = this?.let { if (it.isRecycled) null else it }
+
+/**
+ * Check if not null and recycle if not recycled
+ */
+fun Bitmap?.checkAndRecycle() = withNotNull(this) { if (!isRecycled) recycle() }
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ByteArrayExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ByteArrayExt.kt
new file mode 100644
index 0000000..7530e23
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ByteArrayExt.kt
@@ -0,0 +1,9 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.util.Base64
+import java.io.File
+import java.io.FileOutputStream
+
+fun ByteArray.toFile(file: File) = file.also { FileOutputStream(it).use { stream -> stream.write(this) } }
+
+fun ByteArray?.toBase64() = this?.let { Base64.encodeToString(it, Base64.NO_WRAP) }
\ No newline at end of file
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/CommonExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/CommonExt.kt
new file mode 100644
index 0000000..7c6d90f
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/CommonExt.kt
@@ -0,0 +1,75 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+
+fun safeLet(p1: T1? = null,
+ p2: T2? = null,
+ block: (T1, T2) -> R?): R? =
+ p1?.let { param1 ->
+ p2.takeUnless { it == null }?.let { block(param1, it) }
+ }
+
+fun safeLet(p1: T1? = null,
+ p2: T2? = null,
+ p3: T3? = null,
+ block: (T1, T2, T3) -> R?): R? =
+ safeLet(p1, p2) { param1,
+ param2 ->
+ p3.takeUnless { it == null }?.let { block(param1, param2, it) }
+ }
+
+fun safeLet(p1: T1? = null,
+ p2: T2? = null,
+ p3: T3? = null,
+ p4: T4? = null,
+ block: (T1, T2, T3, T4) -> R?): R? =
+ safeLet(p1, p2, p3) { param1,
+ param2,
+ param3 ->
+ p4.takeUnless { it == null }?.let { block(param1, param2, param3, it) }
+ }
+
+fun safeLet(p1: T1? = null,
+ p2: T2? = null,
+ p3: T3? = null,
+ p4: T4? = null,
+ p5: T5? = null,
+ block: (T1, T2, T3, T4, T5) -> R?): R? =
+ safeLet(p1, p2, p3, p4) { param1,
+ param2,
+ param3,
+ param4 ->
+ p5.takeUnless { it == null }?.let { block(param1, param2, param3, param4, it) }
+ }
+
+fun safeLet(p1: T1? = null,
+ p2: T2? = null,
+ p3: T3? = null,
+ p4: T4? = null,
+ p5: T5? = null,
+ p6: T6? = null,
+ block: (T1, T2, T3, T4, T5, T6) -> R?): R? =
+ safeLet(p1, p2, p3, p4, p5) { param1,
+ param2,
+ param3,
+ param4,
+ param5 ->
+ p6.takeUnless { it == null }?.let { block(param1, param2, param3, param4, param5, it) }
+ }
+
+@Throws(AssertionError::class)
+fun assertInstanceOf(obj: T, clazz: Class<*>, parameterName: String) {
+ check(!clazz.isInstance(obj)) { "$parameterName is not instance of ${clazz.name}." }
+}
+
+inline fun withNotNull(receiver: T?, block: T.() -> R): R? = receiver?.block()
+
+fun applyIf(predicate: Boolean, block: () -> Unit): Boolean = predicate.apply {
+ if (this) block.invoke()
+}
+
+inline fun bindInterface(vararg objects: Any?): T? = objects.find { it is T }
+ ?.let { it as T }
+
+inline fun T.applyIf(predicate: (T) -> Boolean, block: (T) -> Unit): T = apply {
+ if (predicate(this)) block.invoke(this)
+}
\ No newline at end of file
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ContextExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ContextExt.kt
new file mode 100644
index 0000000..87b97b6
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ContextExt.kt
@@ -0,0 +1,69 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.os.Build
+import androidx.annotation.*
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+
+/**
+ * @return a font Typeface associated with a particular resource ID
+ * or throws NotFoundException if the given ID does not exist
+ *
+ * @param id The desired resource identifier.
+ */
+fun Context.getFontResourcesCompat(@FontRes id: Int) = ResourcesCompat.getFont(this, id)
+
+/**
+ * @return a drawable object associated with a particular resource ID
+ * or throws NotFoundException if the given ID does not exist
+ *
+ * @param id The desired resource identifier.
+ */
+fun Context.getDrawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(applicationContext, id)
+
+/**
+ * @return a color object associated with a particular resource ID
+ * or throws NotFoundException if the given ID does not exist
+ *
+ * @param id The desired resource identifier.
+ */
+fun Context.getColorCompat(@ColorRes id: Int) = ContextCompat.getColor(applicationContext, id)
+
+/**
+ * @return a integer object associated with a particular resource ID
+ * or throws NotFoundException if the given ID does not exist0
+ *
+ * @param id The desired resource identifier.
+ */
+fun Context.getInteger(@IntegerRes id: Int) = this.resources.getInteger(id)
+
+/**
+ * @return the string array object associated with a particular resource ID
+ * or throws NotFoundException if the given ID does not exist
+ *
+ * @param id The desired resource identifier.
+ */
+fun Context.getStringArray(@ArrayRes id: Int) = resources.getStringArray(id)
+
+/**
+ * @return true if network connectivity exists, false otherwise.
+ */
+fun Context.isNetworkConnected() = withNotNull(getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager) {
+ @Suppress("DEPRECATION")
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ val wifi = getNetworkInfo(ConnectivityManager.TYPE_WIFI)
+ val mobile = getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
+ wifi.isConnected || mobile.isConnected
+ } else {
+ var connected = false
+ allNetworks
+ .asSequence()
+ .map { network -> getNetworkInfo(network) }
+ .forEach { networkInfo ->
+ connected = connected or networkInfo.isConnected
+ }
+ connected
+ }
+} ?: false
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/EditTextExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/EditTextExt.kt
new file mode 100644
index 0000000..672b2a4
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/EditTextExt.kt
@@ -0,0 +1,70 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.view.MotionEvent
+import android.widget.EditText
+
+fun EditText.getStringText() = this.text.toString()
+
+fun EditText.getTrimStringText() = this.text.trim().toString()
+
+fun EditText.showKeyboardWhenReadyForced() {
+ withNotNull(this) {
+ afterMeasured {
+ requestFocus()
+ context?.showKeyboard()
+ }
+ }
+}
+
+fun EditText.disableEditable() {
+ keyListener = null
+ isFocusableInTouchMode = false
+}
+
+fun EditText.setTextWithSelection(text: String, selection: Int = text.length) {
+ setText(text)
+ setSelection(selection)
+}
+
+fun EditText.setOnDrawableClick(type: DrawablePositionTypes, callback: () -> Unit) {
+ setOnTouchListener { _, event ->
+ if (event.action == MotionEvent.ACTION_UP) {
+
+ when (type) {
+ DrawablePositionTypes.END -> {
+ if (event.rawX >= (right - compoundDrawables[DrawablePositionTypes.END.type].bounds.width())) {
+ callback.invoke()
+ return@setOnTouchListener true
+ }
+ }
+ DrawablePositionTypes.START -> {
+ if (event.rawX <= (compoundDrawables[DrawablePositionTypes.START.type].bounds.width())) {
+ callback.invoke()
+ return@setOnTouchListener true
+ }
+ }
+ else -> return@setOnTouchListener false
+ }
+
+ }
+ return@setOnTouchListener false
+ }
+}
+
+enum class DrawablePositionTypes(val type: Int) {
+ START(0),
+ TOP(1),
+ END(2),
+ BOTTOM(3),
+ UNEXPECTED(-1);
+
+ companion object {
+ fun byValue(value: Int?): DrawablePositionTypes {
+ return DrawablePositionTypes.values().firstOrNull { value == it.type }
+ ?: UNEXPECTED
+ }
+ }
+
+ operator fun invoke() = type
+
+}
\ No newline at end of file
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/FileExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/FileExt.kt
new file mode 100644
index 0000000..a51867b
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/FileExt.kt
@@ -0,0 +1,17 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import androidx.core.content.FileProvider
+import java.io.File
+
+/**
+ * @param context instance of Context[Context]
+ * @param authority the authority of a [FileProvider] defined in a element in your app's manifest
+ *
+ * @return a content URI for the file
+ */
+fun File.getUri(context: Context, authority: String): Uri =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) FileProvider.getUriForFile(context, authority, this)
+ else Uri.fromFile(this)
\ No newline at end of file
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ImageView.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ImageView.kt
new file mode 100644
index 0000000..60e2853
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ImageView.kt
@@ -0,0 +1,9 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.graphics.drawable.BitmapDrawable
+import android.widget.ImageView
+
+/**
+ * @return the bitmap used by view drawable to render
+ */
+fun ImageView.getBitmap() = (this.drawable as? BitmapDrawable)?.bitmap
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/KeyboardExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/KeyboardExt.kt
new file mode 100644
index 0000000..12e5faf
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/KeyboardExt.kt
@@ -0,0 +1,43 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import androidx.fragment.app.Fragment as SupportFragment
+
+internal const val NO_FLAGS = 0
+
+/**
+ * Extension method to provide show keyboard
+ */
+fun Context.showKeyboard() {
+ (getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.apply {
+ toggleSoftInput(InputMethodManager.SHOW_FORCED, NO_FLAGS)
+ }
+}
+
+/**
+ * Extension method to provide show keyboard for Activity androidx
+ */
+fun SupportFragment.showKeyboard() = activity?.showKeyboard()
+
+/**
+ * Extension method to provide hide keyboard for Activity
+ */
+fun Activity.hideKeyboard(view: View? = null) = (this as Context).hideKeyboard(view
+ ?: currentFocus)
+
+/**
+ * Extension method to provide hide keyboard for Context
+ */
+fun Context.hideKeyboard(view: View? = null) = view?.let {
+ (getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.apply {
+ hideSoftInputFromWindow(it.windowToken, NO_FLAGS)
+ }
+}
+
+/**
+ * Extension method to provide hide keyboard for SupportFragment
+ */
+fun SupportFragment.hideKeyboard(view: View? = null) = activity?.hideKeyboard(view)
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ListExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ListExt.kt
new file mode 100644
index 0000000..a62a1e8
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/ListExt.kt
@@ -0,0 +1,3 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+fun List.forEachExcluding(item: T, action: (T) -> Unit) = forEach { if (it !== item) action(it) }
\ No newline at end of file
diff --git a/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/RxExt.kt b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/RxExt.kt
new file mode 100644
index 0000000..e6f42ca
--- /dev/null
+++ b/kotlin-ext/src/main/kotlin/com/cleveroad/bootstrap/kotlin_ext/RxExt.kt
@@ -0,0 +1,137 @@
+package com.cleveroad.bootstrap.kotlin_ext
+
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import io.reactivex.Flowable
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.functions.Consumer
+import io.reactivex.schedulers.Schedulers
+
+// region Observable
+
+fun Observable.workAsync(): Observable =
+ this
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+
+fun Observable.workBackground(): Observable =
+ this
+ .subscribeOn(Schedulers.io())
+ .observeOn(Schedulers.io())
+
+fun Observable.doAsync(successful: Consumer,
+ error: Consumer,
+ loading: MediatorLiveData? = null,
+ isShowProgress: Boolean = true): Disposable =
+ preSubscribe(loading, isShowProgress)
+ .subscribe(successful, error)
+
+fun Observable.doAsync(successful: MutableLiveData