Created
May 7, 2018 18:14
-
-
Save snooplsm/d4480fe02fbffd82eaaa27969aac1e06 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| package cab.reported.nyc.ui.phototaker | |
| import android.annotation.SuppressLint | |
| import android.app.Activity | |
| import android.content.Context | |
| import android.content.Intent | |
| import android.graphics.Bitmap | |
| import android.graphics.BitmapFactory | |
| import android.location.Location | |
| import android.media.MediaMetadataRetriever | |
| import android.net.Uri | |
| import android.os.Bundle | |
| import android.os.Environment | |
| import android.provider.MediaStore | |
| import android.support.design.widget.BottomSheetDialogFragment | |
| import android.support.media.ExifInterface | |
| import android.support.v4.content.FileProvider | |
| import android.view.LayoutInflater | |
| import android.view.View | |
| import android.view.ViewGroup | |
| import cab.reported.nyc.R | |
| import cab.reported.nyc.ReportedSchedulers | |
| import cab.reported.nyc.support.adapter.MediaUtil | |
| import com.crashlytics.android.Crashlytics | |
| import io.reactivex.Single | |
| import kotlinx.android.synthetic.main.fragment_photo_taker_bottom_sheet.* | |
| import java.io.File | |
| import java.io.IOException | |
| import java.text.SimpleDateFormat | |
| import java.util.* | |
| import kotlin.math.roundToInt | |
| /** | |
| * capture: @see https://developer.android.com/training/camera/photobasics.html | |
| * document pick: @see https://developer.android.com/guide/topics/providers/document-provider.html | |
| */ | |
| class PhotoTakerBottomSheetDialog : BottomSheetDialogFragment() { | |
| var listener: PhotoTakerBottomListener? = null | |
| companion object { | |
| const val REQUEST_IMAGE_CAPTURE = 100 | |
| const val REQUEST_VIDEO_CAPTURE = 101 | |
| const val REQUEST_PICK_MEDIA = 102 | |
| } | |
| var takePhoto: Boolean = false | |
| var currentMediaPath: File? = null | |
| override fun onAttach(context: Context?) { | |
| super.onAttach(context) | |
| listener = parentFragment as? PhotoTakerBottomListener | |
| } | |
| override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { | |
| return inflater.inflate(R.layout.fragment_photo_taker_bottom_sheet, container, false); | |
| } | |
| @Throws(IOException::class) | |
| private fun createMediaFile(fileExtension: String = "jpg"): File { | |
| // Create an image file name | |
| val timeStamp = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(Date()) | |
| val imageFileName = timeStamp + "_" | |
| val storageDir = context?.getExternalFilesDir(Environment.DIRECTORY_PICTURES) | |
| val media = File.createTempFile( | |
| imageFileName, /* prefix */ | |
| ".$fileExtension", /* suffix */ | |
| storageDir /* directory */ | |
| ) | |
| currentMediaPath = media.absoluteFile | |
| return media | |
| } | |
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |
| super.onViewCreated(view, savedInstanceState) | |
| val str = getString(R.string.authority_file_provider) | |
| photo_taker_take_photo.setOnClickListener { | |
| takePhoto() | |
| } | |
| photo_taker_take_video.setOnClickListener { | |
| takeVideo() | |
| } | |
| photo_taker_select_photo.setOnClickListener { | |
| pickMedia() | |
| } | |
| } | |
| private fun takePhoto() { | |
| val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) | |
| if (takePictureIntent.resolveActivity(context?.packageManager) != null) { | |
| val photoFile = createMediaFile() | |
| val photoURI = FileProvider.getUriForFile(context!!, | |
| getString(R.string.authority_file_provider), | |
| photoFile) | |
| takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); | |
| startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) | |
| } | |
| } | |
| private fun pickMedia() { | |
| val mediaChooser = Intent(Intent.ACTION_GET_CONTENT) | |
| mediaChooser.type = "image/*" | |
| mediaChooser.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*")) | |
| startActivityForResult(mediaChooser, REQUEST_PICK_MEDIA) | |
| } | |
| private fun takeVideo() { | |
| val takePictureIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) | |
| if (takePictureIntent.resolveActivity(context?.packageManager) != null) { | |
| val photoFile = createMediaFile("mp4") | |
| val photoURI = FileProvider.getUriForFile(context!!, | |
| getString(R.string.authority_file_provider), | |
| photoFile) | |
| takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); | |
| startActivityForResult(takePictureIntent, REQUEST_VIDEO_CAPTURE) | |
| } | |
| } | |
| override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { | |
| when (resultCode == Activity.RESULT_OK) { | |
| true -> { | |
| if (requestCode == REQUEST_IMAGE_CAPTURE) { | |
| captureImage() | |
| } | |
| if (requestCode == REQUEST_VIDEO_CAPTURE) { | |
| captureVideo() | |
| } | |
| if (requestCode == REQUEST_PICK_MEDIA) { | |
| capturePick(data) | |
| } | |
| } | |
| } | |
| } | |
| @SuppressLint("CheckResult") | |
| private fun captureImage() { | |
| Single | |
| .just(currentMediaPath) | |
| .compose(ReportedSchedulers.singleDefault()) | |
| .map { | |
| val bmOptions = BitmapFactory.Options() | |
| bmOptions.inJustDecodeBounds = true | |
| BitmapFactory.decodeFile(currentMediaPath?.absolutePath, bmOptions) | |
| val photoW = bmOptions.outWidth | |
| val photoH = bmOptions.outHeight | |
| val scaleFactor: Int | |
| scaleFactor = Math.min(photoW / 2048, photoH / 2048) | |
| if (scaleFactor != 1) { | |
| bmOptions.inJustDecodeBounds = false | |
| bmOptions.inSampleSize = scaleFactor | |
| val image = BitmapFactory.decodeFile(currentMediaPath?.absolutePath, bmOptions) | |
| image.compress(Bitmap.CompressFormat.JPEG, 80, currentMediaPath?.outputStream()) | |
| image.recycle() | |
| } | |
| } | |
| .map { ExifInterface(currentMediaPath?.absolutePath!!) } | |
| .subscribe({ exif -> | |
| listener?.onPhoto(currentMediaPath!!, exif) | |
| dismiss() | |
| }) | |
| } | |
| private fun captureVideo() { | |
| listener?.onVideo(currentMediaPath!!, null) | |
| dismiss() | |
| } | |
| @SuppressLint("CheckResult") | |
| private fun capturePick(intent: Intent?) { | |
| intent?.let { | |
| val contentType = context?.contentResolver?.getType(it.data) ?: "" | |
| if (MediaUtil.isPhoto(contentType)) { | |
| Single | |
| .just(intent.data) | |
| .compose(ReportedSchedulers.singleDefault()) | |
| .map { | |
| val file: File = copyFile(it) | |
| Pair(file, ExifInterface(file.absolutePath)) | |
| } | |
| .subscribe({ v -> | |
| listener?.onPhoto(v.first, v.second) | |
| dismiss() | |
| }, { | |
| dismiss() | |
| }) | |
| } else { | |
| Single.just(intent?.data) | |
| .compose(ReportedSchedulers.singleDefault()) | |
| .map { | |
| val file = copyFile(it) | |
| val mmr = MediaMetadataRetriever() | |
| mmr.setDataSource(file.absolutePath) | |
| val keys = arrayOf(MediaMetadataRetriever.METADATA_KEY_DATE, MediaMetadataRetriever.METADATA_KEY_LOCATION) | |
| val keyMap = mutableMapOf<Int,Any?>() | |
| keys.forEach { | |
| val data = mmr.extractMetadata(it) ?: return@forEach | |
| when(it) { | |
| MediaMetadataRetriever.METADATA_KEY_DATE -> { | |
| var date = try { | |
| MediaUtil.getExifDateTime(data) | |
| } catch (e: Exception) { | |
| null | |
| } | |
| if(date!=null) { | |
| val oneyearprior = Calendar.getInstance() | |
| oneyearprior.add(Calendar.YEAR,-1) | |
| val oneyearlater = Calendar.getInstance() | |
| oneyearprior.add(Calendar.YEAR,1) | |
| if(date.before(oneyearprior.time) || date.after(oneyearlater.time)) { | |
| date = null | |
| } | |
| } | |
| keyMap[it] = date | |
| } | |
| MediaMetadataRetriever.METADATA_KEY_LOCATION-> { | |
| val builder = StringBuilder() | |
| data.forEachIndexed { index, c -> | |
| if(index!=0 && c=='+' || c=='-') { | |
| builder.append(',') | |
| } | |
| if(c!='+' && c!='/') { | |
| builder.append(c) | |
| } | |
| } | |
| val splits = builder.toString().split(",") | |
| val lat = java.lang.Double.valueOf(splits[0]) | |
| val lon = java.lang.Double.valueOf(splits[1]) | |
| val loc = Location("exif").apply { | |
| latitude = lat | |
| longitude = lon | |
| } | |
| keyMap[it] = loc | |
| } | |
| else-> { | |
| keyMap[it] = mmr.extractMetadata(it) | |
| } | |
| } | |
| } | |
| mmr.release() | |
| Pair(file, keyMap) | |
| } | |
| .doOnSuccess({ | |
| dismiss() | |
| }) | |
| .subscribe({ v -> | |
| listener?.onVideo(v.first, v.second) | |
| }, { e -> | |
| }) | |
| } | |
| } | |
| } | |
| @Throws(IOException::class) | |
| private fun copyFile(uri: Uri): File { | |
| val type = context?.contentResolver?.getType(uri) | |
| val parcelFileDescriptor = context?.contentResolver?.openInputStream(uri) | |
| Crashlytics | |
| .setString("type", type) | |
| Crashlytics.setString("uri", uri.toString()) | |
| val newFile = createMediaFile( | |
| when (type?.startsWith("video")) { | |
| true -> { | |
| "mp4" | |
| } | |
| false -> { | |
| "jpg" | |
| } | |
| null -> { | |
| "jpg" | |
| } | |
| }) | |
| parcelFileDescriptor | |
| ?.copyTo(newFile.outputStream()) | |
| if (newFile.extension == "jpg") { | |
| val bmOptions = BitmapFactory.Options() | |
| bmOptions.inJustDecodeBounds = true | |
| BitmapFactory.decodeFile(newFile.absolutePath, bmOptions) | |
| val photoW = bmOptions.outWidth | |
| val photoH = bmOptions.outHeight | |
| val scaleFactor = Math.max(Math.ceil(photoW / 2048.0), Math.ceil(photoH / 2048.0)).toInt() | |
| if (scaleFactor != 1) { | |
| bmOptions.inJustDecodeBounds = false | |
| bmOptions.inSampleSize = scaleFactor | |
| val image = BitmapFactory.decodeFile(newFile.absolutePath, bmOptions) | |
| image.compress(Bitmap.CompressFormat.JPEG, 80, newFile.outputStream()) | |
| image.recycle() | |
| } | |
| } | |
| Crashlytics.setLong("fileLength", newFile.length()) | |
| Crashlytics.log("copyFile") | |
| return newFile | |
| } | |
| interface PhotoTakerBottomListener { | |
| fun onPhoto(file: File, exifInterface: ExifInterface) | |
| fun onVideo(file: File, metaData: Map<Int,Any?>?) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment