Skip to content

Commit

Permalink
Added Few Features
Browse files Browse the repository at this point in the history
- Close #365
- Close #366
- Close #367
  • Loading branch information
niyajali committed Nov 3, 2023
1 parent 7e606d2 commit b21dcff
Show file tree
Hide file tree
Showing 8 changed files with 467 additions and 272 deletions.
155 changes: 121 additions & 34 deletions core/common/src/main/java/com/niyaj/common/utils/FileUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package com.niyaj.common.utils

import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
Expand All @@ -11,31 +14,34 @@ import android.provider.MediaStore
import android.provider.OpenableColumns
import android.text.TextUtils
import android.util.Log
import androidx.core.content.ContextCompat
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.File
import java.io.FileOutputStream
import java.util.UUID
import kotlin.coroutines.resume

const val FALLBACK_COPY_FOLDER = "upload_part"
const val TAG = "FileUtils"


class FileUtils(var context : Context?) {
class FileUtils(var context: Context?) {

fun getFile(uri : Uri) : File? {
fun getFile(uri: Uri): File? {
return try {
val path = getPath(uri)

if (path != null) {
File(path)
} else null
} catch (e : Exception) {
} catch (e: Exception) {
null
}
}

private fun getPath(uri : Uri) : String? {
val selection : String?
val selectionArgs : Array<String>?
private fun getPath(uri: Uri): String? {
val selection: String?
val selectionArgs: Array<String>?

// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
Expand All @@ -55,7 +61,7 @@ class FileUtils(var context : Context?) {

// DownloadsProvider
if (isDownloadsDocument(uri)) {
var cursor : Cursor? = null
var cursor: Cursor? = null
try {
cursor = context!!.contentResolver.query(
uri, arrayOf(
Expand All @@ -73,7 +79,7 @@ class FileUtils(var context : Context?) {
} finally {
cursor?.close()
}
val id : String = DocumentsContract.getDocumentId(uri)
val id: String = DocumentsContract.getDocumentId(uri)
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
return id.replaceFirst("raw:".toRegex(), "")
Expand All @@ -89,7 +95,7 @@ class FileUtils(var context : Context?) {
id.toLong()
)
getDataColumn(context, contentUri, null, null)
} catch (e : NumberFormatException) {
} catch (e: NumberFormatException) {
//In Android 8 and Android P the id is not a number
uri.path!!.replaceFirst("^/document/raw:".toRegex(), "")
.replaceFirst("^raw:".toRegex(), "")
Expand All @@ -105,7 +111,7 @@ class FileUtils(var context : Context?) {
.toTypedArray()
val type = split[0]

var contentUri : Uri? = null
var contentUri: Uri? = null
when (type) {
"image" -> {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
Expand Down Expand Up @@ -163,16 +169,15 @@ class FileUtils(var context : Context?) {
return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER)
}

private fun fileExists(filePath : String) : Boolean {
private fun fileExists(filePath: String): Boolean {
val file = File(filePath)
return file.exists()
}

private fun getPathFromExtSD(pathData : Array<String>) : String? {
private fun getPathFromExtSD(pathData: Array<String>): String? {
val type = pathData[0]
val relativePath = File.separator + pathData[1]
var fullPath : String

var fullPath: String


// on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string
Expand Down Expand Up @@ -211,7 +216,7 @@ class FileUtils(var context : Context?) {
} else null
}

private fun getDriveFilePath(uri : Uri) : String? {
private fun getDriveFilePath(uri: Uri): String? {
val returnCursor = context!!.contentResolver.query(uri, null, null, null, null)
/*
* Get the column indexes of the data in the Cursor,
Expand All @@ -225,7 +230,7 @@ class FileUtils(var context : Context?) {
try {
val inputStream = context!!.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(file)
var read : Int
var read: Int
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable = inputStream!!.available()

Expand All @@ -237,7 +242,7 @@ class FileUtils(var context : Context?) {
}
inputStream.close()
outputStream.close()
} catch (e : Exception) {
} catch (e: Exception) {
Log.w("File Utils", e)
}

Expand All @@ -251,7 +256,7 @@ class FileUtils(var context : Context?) {
* @param newDirName if you want to create a directory, you can set this variable
* @return
*/
private fun copyFileToInternalStorage(uri : Uri, newDirName : String) : String? {
private fun copyFileToInternalStorage(uri: Uri, newDirName: String): String? {
val returnCursor = context!!.contentResolver.query(
uri, arrayOf(
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
Expand Down Expand Up @@ -283,15 +288,15 @@ class FileUtils(var context : Context?) {
try {
val inputStream = context!!.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(output)
var read : Int
var read: Int
val bufferSize = 1024
val buffers = ByteArray(bufferSize)
while (inputStream!!.read(buffers).also { read = it } != -1) {
outputStream.write(buffers, 0, read)
}
inputStream.close()
outputStream.close()
} catch (e : Exception) {
} catch (e: Exception) {
Log.w("File Utils", e)
}

Expand All @@ -300,17 +305,17 @@ class FileUtils(var context : Context?) {
return output.path
}

private fun getFilePathForWhatsApp(uri : Uri) : String? {
private fun getFilePathForWhatsApp(uri: Uri): String? {
return copyFileToInternalStorage(uri, "whatsapp")
}

private fun getDataColumn(
context : Context?,
uri : Uri?,
selection : String?,
selectionArgs : Array<String>?
) : String? {
var cursor : Cursor? = null
context: Context?,
uri: Uri?,
selection: String?,
selectionArgs: Array<String>?
): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(
column
Expand All @@ -330,27 +335,109 @@ class FileUtils(var context : Context?) {
return null
}

private fun isExternalStorageDocument(uri : Uri) : Boolean {
private fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}

private fun isDownloadsDocument(uri : Uri) : Boolean {
private fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}

private fun isMediaDocument(uri : Uri) : Boolean {
private fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}

private fun isGooglePhotosUri(uri : Uri) : Boolean {
private fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}

private fun isWhatsAppFile(uri : Uri) : Boolean {
private fun isWhatsAppFile(uri: Uri): Boolean {
return "com.whatsapp.provider.media" == uri.authority
}

private fun isGoogleDriveUri(uri : Uri) : Boolean {
private fun isGoogleDriveUri(uri: Uri): Boolean {
return "com.google.android.apps.docs.storage" == uri.authority || "com.google.android.apps.docs.storage.legacy" == uri.authority
}
}
}


suspend fun Bitmap.saveToDisk(context: Context): Uri {
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
"screenshot-${System.currentTimeMillis()}.png"
)

file.writeBitmap(this, Bitmap.CompressFormat.PNG, 100)

return scanFilePath(context, file.path) ?: throw Exception("File could not be saved")
}

/**
* We call [MediaScannerConnection] to index the newly created image inside MediaStore to be visible
* for other apps, as well as returning its [MediaStore] Uri
*/
suspend fun scanFilePath(context: Context, filePath: String): Uri? {
return suspendCancellableCoroutine { continuation ->
MediaScannerConnection.scanFile(
context,
arrayOf(filePath),
arrayOf("image/png")
) { _, scannedUri ->
if (scannedUri == null) {
continuation.cancel(Exception("File $filePath could not be scanned"))
} else {
continuation.resume(scannedUri)
}
}
}
}

fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {
outputStream().use { out ->
bitmap.compress(format, quality, out)
out.flush()
}
}

fun shareBitmap(context: Context, uri: Uri) {
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/png"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
ContextCompat.startActivity(context, Intent.createChooser(intent, "Share your image"), null)
}


//val writeStorageAccessState = rememberMultiplePermissionsState(
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// // No permissions are needed on Android 10+ to add files in the shared storage
// emptyList()
// } else {
// listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
// }
//)
//// This logic should live in your ViewModel - trigger a side effect to invoke URI sharing.
//// checks permissions granted, and then saves the bitmap from a Picture that is already capturing content
//// and shares it with the default share sheet.
//fun shareBitmapFromComposable(bitmap: Bitmap) {
// if (writeStorageAccessState.allPermissionsGranted) {
// scope.launch {
// val uri = bitmap.saveToDisk(context)
// shareBitmap(context, uri)
// }
// } else if (writeStorageAccessState.shouldShowRationale) {
// scope.launch {
// val result = snackbarState.showSnackbar(
// message = "The storage permission is needed to save the image",
// actionLabel = "Grant Access"
// )
//
// if (result == SnackbarResult.ActionPerformed) {
// writeStorageAccessState.launchMultiplePermissionRequest()
// }
// }
// } else {
// writeStorageAccessState.launchMultiplePermissionRequest()
// }
//}
35 changes: 35 additions & 0 deletions core/ui/src/main/java/com/niyaj/ui/components/GridTexts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp

@Composable
fun TwoGridTexts(
Expand Down Expand Up @@ -146,4 +147,38 @@ fun TextDivider(
.weight(1f, true)
)
}
}

@Composable
fun AnimatedTextDivider(
modifier: Modifier = Modifier,
text: String,
textStyle: TextStyle = MaterialTheme.typography.bodySmall,
fontWeight: FontWeight = FontWeight.SemiBold,
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
HorizontalDivider(
modifier = Modifier
.weight(1f, true)
.drawRainbowBorder(1.dp, durationMillis = 5000)
)

Text(
text = text,
style = textStyle,
fontWeight = fontWeight,
textAlign = TextAlign.Center,
modifier = Modifier.weight(1f)
)

HorizontalDivider(
modifier = Modifier
.weight(1f, true)
.drawRainbowBorder(1.dp, durationMillis = 5000)
)
}
}
22 changes: 20 additions & 2 deletions core/ui/src/main/java/com/niyaj/ui/components/StandardScaffold.kt
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ fun StandardScaffoldNew(
title: String,
showDrawer: Boolean = true,
showBackButton: Boolean = false,
showBottomBar: Boolean,
showBottomBar: Boolean = false,
showFab: Boolean = false,
fabPosition: FabPosition = FabPosition.Center,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
onBackClick: () -> Unit = { navController.navigateUp() },
Expand Down Expand Up @@ -339,7 +340,24 @@ fun StandardScaffoldNew(
bottomBar()
}
},
floatingActionButton = floatingActionButton,
floatingActionButton = {
AnimatedVisibility(
visible = showFab,
label = "BottomBar",
enter = fadeIn() + slideInVertically(
initialOffsetY = { fullHeight ->
fullHeight / 4
}
),
exit = fadeOut() + slideOutVertically(
targetOffsetY = { fullHeight ->
fullHeight / 4
}
)
) {
floatingActionButton()
}
},
floatingActionButtonPosition = fabPosition,
snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier
Expand Down
Loading

0 comments on commit b21dcff

Please sign in to comment.