Skip to content

Commit

Permalink
fix: Selected image get rotate in edit profile (#1270)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aman Bansal committed Mar 11, 2019
1 parent 866301a commit 5913160
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 18 deletions.
139 changes: 139 additions & 0 deletions app/src/main/java/org/fossasia/openevent/general/RotateBitmap.kt
@@ -0,0 +1,139 @@
package org.fossasia.openevent.general

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import android.util.Log

import java.io.IOException

import androidx.exifinterface.media.ExifInterface

/**
* Created by aman210697 on 11/02/2019.
*/

class RotateBitmap {

private var mContext: Context? = null

/**
* This method is responsible for solving the rotation issue if exist. Also scale the images to
* 1024x1024 resolution
*
* @param selectedImage The Image URI
* @return Bitmap image results
* @throws IOException
*/
@Throws(IOException::class)
fun HandleSamplingAndRotationBitmap(context: Context, selectedImage: Uri): Bitmap? {
mContext = context
val MAX_HEIGHT = 1024
val MAX_WIDTH = 1024

// First decode with inJustDecodeBounds=true to check dimensions
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
var imageStream = context.contentResolver.openInputStream(selectedImage)
BitmapFactory.decodeStream(imageStream, null, options)
imageStream!!.close()

// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT)

// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false
imageStream = context.contentResolver.openInputStream(selectedImage)
var img = BitmapFactory.decodeStream(imageStream, null, options)

img = rotateImageIfRequired(img, selectedImage)
return img
}

/**
* Rotate an image if required.
*
* @param img The image bitmap
* @param selectedImage Image URI
* @return The resulted Bitmap after manipulation
*/
@Throws(IOException::class)
private fun rotateImageIfRequired(img: Bitmap?, selectedImage: Uri): Bitmap? {

val input = mContext!!.contentResolver.openInputStream(selectedImage)
val ei: ExifInterface
try {
ei = ExifInterface(input!!)

val orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)

when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> return rotateImage(img, 90)
ExifInterface.ORIENTATION_ROTATE_180 -> return rotateImage(img, 180)
ExifInterface.ORIENTATION_ROTATE_270 -> return rotateImage(img, 270)
else -> return img
}
} catch (e: NullPointerException) {
Log.e(TAG, "rotateImageIfRequired: Could not read file." + e.message)
}

return img
}

companion object {

private val TAG = "RotateBitmap"

/*
----------------------------- Image Rotation --------------------------------------------------
*/

private fun rotateImage(img: Bitmap?, degree: Int): Bitmap {
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
val rotatedImg = Bitmap.createBitmap(img!!, 0, 0, img.width, img.height, matrix, true)
img.recycle()
return rotatedImg
}

private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int
): Int {
// Raw height and width of image
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1

if (height > reqHeight || width > reqWidth) {

// Calculate ratios of height and width to requested height and width
val heightRatio = Math.round(height.toFloat() / reqHeight.toFloat())
val widthRatio = Math.round(width.toFloat() / reqWidth.toFloat())

// Choose the smallest ratio as inSampleSize value, this will guarantee a final image
// with both dimensions larger than or equal to the requested height and width.
inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio

// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).

val totalPixels = (width * height).toFloat()

// Anything more than 2x the requested pixels we'll sample down further
val totalReqPixelsCap = (reqWidth * reqHeight * 2).toFloat()

while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++
}
}
return inSampleSize
}
}
}
Expand Up @@ -6,7 +6,6 @@ import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Base64
import android.view.LayoutInflater
Expand All @@ -27,29 +26,30 @@ import kotlinx.android.synthetic.main.fragment_edit_profile.view.profilePhoto
import kotlinx.android.synthetic.main.fragment_edit_profile.view.progressBar
import org.fossasia.openevent.general.CircleTransform
import org.fossasia.openevent.general.R
import org.fossasia.openevent.general.RotateBitmap
import org.fossasia.openevent.general.utils.Utils.hideSoftKeyboard
import org.fossasia.openevent.general.utils.Utils.requireDrawable
import org.fossasia.openevent.general.utils.extensions.nonNull
import org.fossasia.openevent.general.utils.nullToEmpty
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream

class EditProfileFragment : Fragment() {

private val profileViewModel by viewModel<ProfileViewModel>()
private val editProfileViewModel by viewModel<EditProfileViewModel>()
private lateinit var rootView: View
private var permissionGranted = false
private var encodedImage: String? = null
private val PICK_IMAGE_REQUEST = 100
private val READ_STORAGE = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
private val REQUEST_CODE = 1

private lateinit var userFirstName: String
private lateinit var userLastName: String
private var avatarUpdated: Boolean = false

override fun onCreateView(
inflater: LayoutInflater,
Expand Down Expand Up @@ -93,7 +93,7 @@ class EditProfileFragment : Fragment() {

rootView.buttonUpdate.setOnClickListener {
hideSoftKeyboard(context, rootView)
editProfileViewModel.updateProfile(encodedImage, rootView.firstName.text.toString(),
editProfileViewModel.updateProfile(rootView.firstName.text.toString(),
rootView.lastName.text.toString())
}

Expand All @@ -106,6 +106,14 @@ class EditProfileFragment : Fragment() {
}
})

editProfileViewModel.updatedImageTemp.nonNull().observe(this, Observer {
Picasso.get()
.load(it)
.placeholder(requireDrawable(requireContext(), R.drawable.ic_person_black))
.transform(CircleTransform())
.into(rootView.profilePhoto)
})

return rootView
}

Expand All @@ -115,19 +123,13 @@ class EditProfileFragment : Fragment() {
val imageUri = intentData.data ?: return

try {
val imageStream = activity?.contentResolver?.openInputStream(imageUri)
val selectedImage = BitmapFactory.decodeStream(imageStream)
encodedImage = encodeImage(selectedImage)
val selectedImage = RotateBitmap().HandleSamplingAndRotationBitmap(requireContext(), imageUri)
editProfileViewModel.encodedImage.value = encodeImage(selectedImage!!)
} catch (e: FileNotFoundException) {
Timber.d(e, "File Not Found Exception")
}

Picasso.get()
.load(imageUri)
.placeholder(requireDrawable(requireContext(), R.drawable.ic_person_black))
.transform(CircleTransform())
.into(rootView.profilePhoto)
avatarUpdated = true
editProfileViewModel.avatarUpdated.value = true
}
}

Expand All @@ -136,6 +138,19 @@ class EditProfileFragment : Fragment() {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val bytes = baos.toByteArray()

//create temp file
editProfileViewModel.updatedImageTemp.value = File(context?.cacheDir, "temp")
editProfileViewModel.updatedImageTemp.value?.createNewFile()
val fos = FileOutputStream(editProfileViewModel.updatedImageTemp.value)
fos.write(bytes)
fos.flush()
fos.close()
Picasso.get()
.load(editProfileViewModel.updatedImageTemp.value!!)
.placeholder(requireDrawable(requireContext(), R.drawable.ic_person_black))
.transform(CircleTransform())
.into(rootView.profilePhoto)

return "data:image/jpeg;base64," + Base64.encodeToString(bytes, Base64.DEFAULT)
}

Expand All @@ -149,7 +164,8 @@ class EditProfileFragment : Fragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
if (!avatarUpdated && rootView.lastName.text.toString() == userLastName &&
if (!editProfileViewModel.avatarUpdated.value!! &&
rootView.lastName.text.toString() == userLastName &&
rootView.firstName.text.toString() == userFirstName) {
activity?.onBackPressed()
} else {
Expand All @@ -160,7 +176,7 @@ class EditProfileFragment : Fragment() {
activity?.onBackPressed()
}
dialog.setPositiveButton("Save") { _, _ ->
editProfileViewModel.updateProfile(encodedImage, rootView.firstName.text.toString(),
editProfileViewModel.updateProfile(rootView.firstName.text.toString(),
rootView.lastName.text.toString())
}
dialog.create().show()
Expand Down
Expand Up @@ -8,6 +8,7 @@ import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import org.fossasia.openevent.general.common.SingleLiveEvent
import timber.log.Timber
import java.io.File

const val USER_UPDATED = "User updated successfully!"

Expand All @@ -25,14 +26,18 @@ class EditProfileViewModel(
private val mutableMessage = SingleLiveEvent<String>()
val message: LiveData<String> = mutableMessage

var avatarUpdated = MutableLiveData<Boolean>(false)
var encodedImage = MutableLiveData<String>()
var updatedImageTemp = MutableLiveData<File>()

fun isLoggedIn() = authService.isLoggedIn()

fun updateProfile(encodedImage: String?, firstName: String, lastName: String) {
if (encodedImage.isNullOrEmpty()) {
fun updateProfile(firstName: String, lastName: String) {
if (encodedImage.value.isNullOrEmpty()) {
updateUser(null, firstName, lastName)
return
}
compositeDisposable.add(authService.uploadImage(UploadImage(encodedImage))
compositeDisposable.add(authService.uploadImage(UploadImage(encodedImage.value))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Expand Down

0 comments on commit 5913160

Please sign in to comment.