forked from Expensify/App
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
android setup
- Loading branch information
Showing
17 changed files
with
363 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
android/app/src/main/java/com/expensify/chat/ShareActionHandlerModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.expensify.chat | ||
|
||
import android.content.Context | ||
import android.util.Log | ||
import com.expensify.chat.intentHandler.IntentHandlerConstants | ||
import com.facebook.react.bridge.Arguments | ||
import com.facebook.react.bridge.Callback | ||
import com.facebook.react.bridge.ReactApplicationContext | ||
import com.facebook.react.bridge.ReactContextBaseJavaModule | ||
import com.facebook.react.bridge.ReactMethod | ||
|
||
class ShareActionHandlerModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { | ||
override fun getName() = "ShareActionHandlerModule" | ||
|
||
@ReactMethod | ||
fun processFiles(callback: Callback) { | ||
try { | ||
val sharedPreferences = reactApplicationContext.getSharedPreferences(IntentHandlerConstants.preferencesFile, Context.MODE_PRIVATE) | ||
val fileSet = sharedPreferences.getStringSet(IntentHandlerConstants.fileArrayProperty, setOf()) | ||
val fileArray: ArrayList<String> = ArrayList(fileSet) | ||
|
||
val resultArray = Arguments.createArray() | ||
for (file in fileArray) { | ||
resultArray.pushString(file) | ||
} | ||
|
||
callback.invoke(resultArray) | ||
} catch (exception: Exception) { | ||
Log.e("ImageIntentHandler", exception.toString()) | ||
callback.invoke(exception.toString(), null) | ||
} | ||
} | ||
|
||
} |
114 changes: 114 additions & 0 deletions
114
android/app/src/main/java/com/expensify/chat/image/ImageUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package com.expensify.chat.image | ||
|
||
import android.content.Context | ||
import android.net.Uri | ||
import android.os.Environment | ||
import android.util.Log | ||
import java.io.File | ||
import java.io.FileOutputStream | ||
import java.io.IOException | ||
import java.io.InputStream | ||
import java.io.OutputStream | ||
|
||
|
||
object ImageUtils { | ||
private const val tag = "ImageUtils" | ||
private const val maxPictureWidth = 2400 | ||
private const val optimalPictureSize = maxPictureWidth * maxPictureWidth | ||
private const val defaultImageExtension = ".jpg" | ||
|
||
private fun getUniqueImageFilePrefix(): String { | ||
return System.currentTimeMillis().toString() | ||
} | ||
|
||
/** | ||
* Checks if external storage is available. | ||
* | ||
* @return true if external storage is available, mounted and writable, false otherwise. | ||
*/ | ||
private fun isExternalStorageAvailable(): Boolean { | ||
// Make sure the media storage is mounted (MEDIA_MOUNTED implies writable) | ||
val state: String = Environment.getExternalStorageState() | ||
if (state != Environment.MEDIA_MOUNTED) { | ||
Log.w(tag, "External storage requested but not available" ) | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
/** | ||
* Synchronous method | ||
* | ||
* @param context | ||
* @param imageUri | ||
* @param destinationFile | ||
* @throws IOException | ||
*/ | ||
@Throws(IOException::class) | ||
fun saveImageFromMediaProviderUri(imageUri: Uri?, destinationFile: File?, context: Context) { | ||
val inputStream: InputStream? = imageUri?.let { context.contentResolver.openInputStream(it) } | ||
val outputStream: OutputStream = FileOutputStream(destinationFile) | ||
inputStream?.use { input -> | ||
outputStream.use { output -> | ||
input.copyTo(output) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Creates an image file into the internal or external storage. | ||
* | ||
* @param context | ||
* @return | ||
* @throws IOException | ||
*/ | ||
@Throws(IOException::class) | ||
fun createImageFile(context: Context): File { | ||
|
||
// Create an image file name | ||
val file: File = File.createTempFile( | ||
getUniqueImageFilePrefix(), | ||
defaultImageExtension, | ||
getPhotoDirectory(context) | ||
) | ||
Log.i(tag, "Created a temporary file for the photo at" + file.absolutePath) | ||
return file | ||
} | ||
|
||
/** | ||
* Determines where the photo directory is based on if we can use external storage or not | ||
* | ||
* @param context | ||
* @return File the directory | ||
*/ | ||
private fun getPhotoDirectory(context: Context): File? { | ||
val photoDirectory: File = if (isExternalStorageAvailable()) File( | ||
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "Expensify" | ||
) else File(context.filesDir.absolutePath, "Expensify") | ||
if (!photoDirectory.exists()) { | ||
photoDirectory.mkdir() | ||
} | ||
return photoDirectory | ||
} | ||
|
||
/** | ||
* Copy the given Uri to storage | ||
* | ||
* @param uri | ||
* @param context | ||
* @return The absolute path of the image | ||
*/ | ||
fun copyUriToStorage(uri: Uri?, context: Context): String? { | ||
var resultingPath: String? = null | ||
try { | ||
val imageFile: File = createImageFile(context) | ||
saveImageFromMediaProviderUri(uri, imageFile, context) | ||
resultingPath = imageFile.absolutePath | ||
Log.i("ImageIntentHandler", "save image$resultingPath") | ||
|
||
} catch (ex: IOException) { | ||
Log.e(tag, "Couldn't save image from intent", ex) | ||
} | ||
return resultingPath | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
android/app/src/main/java/com/expensify/chat/intentHandler/AbstractIntentHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.expensify.chat.intentHandler | ||
|
||
abstract class AbstractIntentHandler: IntentHandler { | ||
override fun onCompleted() {} | ||
} |
87 changes: 87 additions & 0 deletions
87
android/app/src/main/java/com/expensify/chat/intentHandler/ImageIntentHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.expensify.chat.intentHandler | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import android.net.Uri | ||
import android.util.Log | ||
import com.expensify.chat.image.ImageUtils.copyUriToStorage | ||
|
||
|
||
object IntentHandlerConstants { | ||
const val preferencesFile = "shareActionHandler" | ||
const val fileArrayProperty = "filePaths" | ||
} | ||
|
||
|
||
class ImageIntentHandler(private val context: Context) : AbstractIntentHandler() { | ||
override fun handle(intent: Intent?): Boolean { | ||
Log.i("ImageIntentHandler", "Handle intent" + intent.toString()) | ||
if (intent == null) { | ||
return false | ||
} | ||
|
||
val action: String? = intent.action | ||
val type: String = intent.type ?: return false | ||
|
||
if(!type.startsWith("image/")) return false | ||
|
||
when(action) { | ||
Intent.ACTION_SEND -> { | ||
Log.i("ImageIntentHandler", "Handle receive single image") | ||
handleSingleImageIntent(intent, context) | ||
onCompleted() | ||
return true | ||
} | ||
Intent.ACTION_SEND_MULTIPLE -> { | ||
handleMultipleImagesIntent(intent, context) | ||
onCompleted() | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
private fun handleSingleImageIntent(intent: Intent, context: Context) { | ||
(intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM))?.let { imageUri -> | ||
|
||
Log.i("ImageIntentHandler", "handleSingleImageIntent$imageUri") | ||
// Update UI to reflect image being shared | ||
if (imageUri == null) { | ||
return | ||
} | ||
|
||
val fileArrayList: ArrayList<String> = ArrayList() | ||
val resultingPath: String? = copyUriToStorage(imageUri, context) | ||
if (resultingPath != null) { | ||
fileArrayList.add(resultingPath) | ||
val sharedPreferences = context.getSharedPreferences(IntentHandlerConstants.preferencesFile, Context.MODE_PRIVATE) | ||
val editor = sharedPreferences.edit() | ||
editor.putStringSet(IntentHandlerConstants.fileArrayProperty, fileArrayList.toSet()) | ||
editor.apply() | ||
} | ||
} | ||
} | ||
|
||
private fun handleMultipleImagesIntent(intent: Intent, context: Context) { | ||
|
||
val resultingImagePaths = ArrayList<String>() | ||
|
||
(intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM))?.let { imageUris -> | ||
for (imageUri in imageUris) { | ||
val resultingPath: String? = copyUriToStorage(imageUri, context) | ||
if (resultingPath != null) { | ||
resultingImagePaths.add(resultingPath) | ||
} | ||
} | ||
} | ||
// Yapl.getInstance().callIntentCallback(resultingImagePaths) | ||
} | ||
|
||
override fun onCompleted() { | ||
val uri: Uri = Uri.parse("new-expensify://share/root") | ||
val deepLinkIntent = Intent(Intent.ACTION_VIEW, uri) | ||
deepLinkIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK | ||
context.startActivity(deepLinkIntent) | ||
} | ||
|
||
} |
8 changes: 8 additions & 0 deletions
8
android/app/src/main/java/com/expensify/chat/intentHandler/IntentHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.expensify.chat.intentHandler | ||
|
||
import android.content.Intent | ||
|
||
interface IntentHandler { | ||
fun handle(intent: Intent?): Boolean | ||
fun onCompleted() | ||
} |
14 changes: 14 additions & 0 deletions
14
android/app/src/main/java/com/expensify/chat/intentHandler/IntentHandlerFactory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.expensify.chat.intentHandler | ||
|
||
import android.content.Context | ||
|
||
object IntentHandlerFactory { | ||
fun getIntentHandler(context: Context, mimeType: String?): IntentHandler? { | ||
if (mimeType == null) return null | ||
return when { | ||
mimeType.startsWith("image/") -> ImageIntentHandler(context) | ||
// Add other cases like video/*, application/pdf etc. | ||
else -> null | ||
} | ||
} | ||
} |
Oops, something went wrong.