Unverified Commit c448359d authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #1384 from RocketChat/feature/compress-image

[NEW] Compress image
parents 18e06ae0 590aedde
...@@ -5,26 +5,32 @@ import android.net.Uri ...@@ -5,26 +5,32 @@ import android.net.Uri
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import javax.inject.Inject import javax.inject.Inject
class UriInteractor @Inject constructor(private val context: Context) { class UriInteractor @Inject constructor(private val context: Context) {
/** /**
* Gets the file name from an [Uri]. * Returns the file name from the [Uri].
*/ */
fun getFileName(uri: Uri): String? = uri.getFileName(context) fun getFileName(uri: Uri): String? = uri.getFileName(context)
/** /**
* Gets the MimeType of an [Uri] * Returns the MimeType from the [Uri].
*/ */
fun getMimeType(uri: Uri): String = uri.getMimeType(context) fun getMimeType(uri: Uri): String = uri.getMimeType(context)
/** /**
* Gets the real path of an [Uri] * Returns the file size from the [Uri].
*/ */
fun getRealPath(uri: Uri): String? = uri.getRealPathFromURI(context)
fun getFileSize(uri: Uri) = uri.getFileSize(context) fun getFileSize(uri: Uri) = uri.getFileSize(context)
/**
* Returns the InputStream from the [Uri].
*/
fun getInputStream(uri: Uri) = uri.getInputStream(context) fun getInputStream(uri: Uri) = uri.getInputStream(context)
/**
* Returns the Bitmap from the [Uri].
*
* Note: It should be an image.
*/
fun getBitmap(uri: Uri) = uri.getBitmpap(context)
} }
\ No newline at end of file
...@@ -16,21 +16,11 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -16,21 +16,11 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.MessageHelper import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.UserHelper import chat.rocket.android.helper.UserHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.ChatRoomsInteractor import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.JobSchedulerInteractor
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.RoomRepository
import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.domain.uploadMaxFileSize
import chat.rocket.android.server.domain.uploadMimeTypeFilter
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.state import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.getCompressFormat
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
...@@ -43,36 +33,22 @@ import chat.rocket.core.internal.realtime.setTypingStatus ...@@ -43,36 +33,22 @@ import chat.rocket.core.internal.realtime.setTypingStatus
import chat.rocket.core.internal.realtime.socket.model.State import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.realtime.subscribeTypingStatus import chat.rocket.core.internal.realtime.subscribeTypingStatus
import chat.rocket.core.internal.realtime.unsubscribe import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.chatRoomRoles import chat.rocket.core.internal.rest.*
import chat.rocket.core.internal.rest.commands
import chat.rocket.core.internal.rest.deleteMessage
import chat.rocket.core.internal.rest.getMembers
import chat.rocket.core.internal.rest.history
import chat.rocket.core.internal.rest.joinChat
import chat.rocket.core.internal.rest.markAsRead
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.messages
import chat.rocket.core.internal.rest.pinMessage
import chat.rocket.core.internal.rest.runCommand
import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.internal.rest.spotlight
import chat.rocket.core.internal.rest.starMessage
import chat.rocket.core.internal.rest.toggleReaction
import chat.rocket.core.internal.rest.unpinMessage
import chat.rocket.core.internal.rest.unstarMessage
import chat.rocket.core.internal.rest.updateMessage
import chat.rocket.core.internal.rest.uploadFile
import chat.rocket.core.model.ChatRoomRole import chat.rocket.core.model.ChatRoomRole
import chat.rocket.core.model.Command import chat.rocket.core.model.Command
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import org.threeten.bp.Instant import org.threeten.bp.Instant
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
...@@ -111,7 +87,12 @@ class ChatRoomPresenter @Inject constructor( ...@@ -111,7 +87,12 @@ class ChatRoomPresenter @Inject constructor(
private var lastState = manager.state private var lastState = manager.state
private var typingStatusList = arrayListOf<String>() private var typingStatusList = arrayListOf<String>()
fun setupChatRoom(roomId: String, roomName: String, roomType: String, chatRoomMessage: String? = null) { fun setupChatRoom(
roomId: String,
roomName: String,
roomType: String,
chatRoomMessage: String? = null
) {
launchUI(strategy) { launchUI(strategy) {
try { try {
chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) { chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) {
...@@ -130,10 +111,16 @@ class ChatRoomPresenter @Inject constructor( ...@@ -130,10 +111,16 @@ class ChatRoomPresenter @Inject constructor(
} ?: false } ?: false
view.onRoomUpdated(userCanPost, chatIsBroadcast, userCanMod) view.onRoomUpdated(userCanPost, chatIsBroadcast, userCanMod)
loadMessages(roomId, roomType) loadMessages(roomId, roomType)
chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) }?.let { messageId -> chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) }
val name = messageHelper.roomNameFromPermalink(chatRoomMessage) ?.let { messageId ->
citeMessage(name!!, messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, messageId, true) val name = messageHelper.roomNameFromPermalink(chatRoomMessage)
} citeMessage(
name!!,
messageHelper.roomTypeFromPermalink(chatRoomMessage)!!,
messageId,
true
)
}
} }
} }
} }
...@@ -152,8 +139,12 @@ class ChatRoomPresenter @Inject constructor( ...@@ -152,8 +139,12 @@ class ChatRoomPresenter @Inject constructor(
try { try {
if (offset == 0L) { if (offset == 0L) {
val localMessages = messagesRepository.getByRoomId(chatRoomId) val localMessages = messagesRepository.getByRoomId(chatRoomId)
val oldMessages = mapper.map(localMessages, RoomUiModel(roles = chatRoles, val oldMessages = mapper.map(
isBroadcast = chatIsBroadcast, isRoom = true)) localMessages, RoomUiModel(
roles = chatRoles,
isBroadcast = chatIsBroadcast, isRoom = true
)
)
if (oldMessages.isNotEmpty()) { if (oldMessages.isNotEmpty()) {
view.showMessages(oldMessages) view.showMessages(oldMessages)
loadMissingMessages() loadMissingMessages()
...@@ -185,14 +176,24 @@ class ChatRoomPresenter @Inject constructor( ...@@ -185,14 +176,24 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
private suspend fun loadAndShowMessages(chatRoomId: String, chatRoomType: String, offset: Long = 0) { private suspend fun loadAndShowMessages(
chatRoomId: String,
chatRoomType: String,
offset: Long = 0
) {
val messages = val messages =
retryIO(description = "messages chatRoom: $chatRoomId, type: $chatRoomType, offset: $offset") { retryIO(description = "messages chatRoom: $chatRoomId, type: $chatRoomType, offset: $offset") {
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
} }
messagesRepository.saveAll(messages) messagesRepository.saveAll(messages)
view.showMessages(mapper.map(messages, RoomUiModel(roles = chatRoles, view.showMessages(
isBroadcast = chatIsBroadcast, isRoom = true))) mapper.map(
messages, RoomUiModel(
roles = chatRoles,
isBroadcast = chatIsBroadcast, isRoom = true
)
)
)
} }
fun sendMessage(chatRoomId: String, text: String, messageId: String?) { fun sendMessage(chatRoomId: String, text: String, messageId: String?) {
...@@ -227,8 +228,13 @@ class ChatRoomPresenter @Inject constructor( ...@@ -227,8 +228,13 @@ class ChatRoomPresenter @Inject constructor(
) )
try { try {
messagesRepository.save(newMessage) messagesRepository.save(newMessage)
view.showNewMessage(mapper.map(newMessage, RoomUiModel( view.showNewMessage(
roles = chatRoles, isBroadcast = chatIsBroadcast))) mapper.map(
newMessage, RoomUiModel(
roles = chatRoles, isBroadcast = chatIsBroadcast
)
)
)
client.sendMessage(id, chatRoomId, text) client.sendMessage(id, chatRoomId, text)
} catch (ex: Exception) { } catch (ex: Exception) {
// Ok, not very beautiful, but the backend sends us a not valid response // Ok, not very beautiful, but the backend sends us a not valid response
...@@ -265,19 +271,37 @@ class ChatRoomPresenter @Inject constructor( ...@@ -265,19 +271,37 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
val fileName = async { uriInteractor.getFileName(uri) }.await() ?: uri.toString() withContext(DefaultDispatcher) {
val mimeType = async { uriInteractor.getMimeType(uri) }.await() val fileName = uriInteractor.getFileName(uri) ?: uri.toString()
val fileSize = async { uriInteractor.getFileSize(uri) }.await() val fileSize = uriInteractor.getFileSize(uri)
val maxFileSize = settings.uploadMaxFileSize() val mimeType = uriInteractor.getMimeType(uri)
val maxFileSizeAllowed = settings.uploadMaxFileSize()
when {
fileName.isNullOrEmpty() -> view.showInvalidFileMessage() when {
fileSize > maxFileSize -> view.showInvalidFileSize(fileSize, maxFileSize) fileName.isEmpty() -> {
else -> { view.showInvalidFileMessage()
Timber.d("Uploading to $roomId: $fileName - $mimeType") }
retryIO("uploadFile($roomId, $fileName, $mimeType") { fileSize > maxFileSizeAllowed -> {
client.uploadFile(roomId, fileName!!, mimeType, msg, description = fileName) { view.showInvalidFileSize(fileSize, maxFileSizeAllowed)
uriInteractor.getInputStream(uri) }
else -> {
var inputStream: InputStream? = uriInteractor.getInputStream(uri)
if (mimeType.contains("image")) {
compressImage(uri, mimeType)?.let {
inputStream = it
}
}
retryIO("uploadFile($roomId, $fileName, $mimeType") {
client.uploadFile(
roomId,
fileName,
mimeType,
msg,
description = fileName
) {
inputStream
}
} }
} }
} }
...@@ -294,6 +318,25 @@ class ChatRoomPresenter @Inject constructor( ...@@ -294,6 +318,25 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
// Returns an InputStream of a compressed image.
private suspend fun compressImage(uri: Uri, mimeType: String): InputStream? {
var inputStream: InputStream? = null
uriInteractor.getBitmap(uri)?.let {
withContext(DefaultDispatcher) {
val byteArrayOutputStream = ByteArrayOutputStream()
// TODO: Add an option the the app to the user be able to select the quality of the compressed image
val isCompressed =
it.compress(it.getCompressFormat(mimeType), 70, byteArrayOutputStream)
if (isCompressed) {
inputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
}
}
}
return inputStream
}
fun sendTyping() { fun sendTyping() {
launch(CommonPool + strategy.jobs) { launch(CommonPool + strategy.jobs) {
if (chatRoomId != null && currentLoggedUsername != null) { if (chatRoomId != null && currentLoggedUsername != null) {
...@@ -362,15 +405,23 @@ class ChatRoomPresenter @Inject constructor( ...@@ -362,15 +405,23 @@ class ChatRoomPresenter @Inject constructor(
.sortedByDescending { it.timestamp }.firstOrNull()?.let { lastMessage -> .sortedByDescending { it.timestamp }.firstOrNull()?.let { lastMessage ->
val instant = Instant.ofEpochMilli(lastMessage.timestamp).toString() val instant = Instant.ofEpochMilli(lastMessage.timestamp).toString()
try { try {
val messages = retryIO(description = "history($chatRoomId, $roomType, $instant)") { val messages =
client.history(chatRoomId!!, roomType, count = 50, retryIO(description = "history($chatRoomId, $roomType, $instant)") {
oldest = instant) client.history(
} chatRoomId!!, roomType, count = 50,
oldest = instant
)
}
Timber.d("History: $messages") Timber.d("History: $messages")
if (messages.result.isNotEmpty()) { if (messages.result.isNotEmpty()) {
val models = mapper.map(messages.result, RoomUiModel( val models = mapper.map(
roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true)) messages.result, RoomUiModel(
roles = chatRoles,
isBroadcast = chatIsBroadcast,
isRoom = true
)
)
messagesRepository.saveAll(messages.result) messagesRepository.saveAll(messages.result)
launchUI(strategy) { launchUI(strategy) {
...@@ -440,7 +491,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -440,7 +491,8 @@ class ChatRoomPresenter @Inject constructor(
val id = msg.id val id = msg.id
val username = msg.sender?.username ?: "" val username = msg.sender?.username ?: ""
val mention = if (mentionAuthor && me?.username != username) "@$username" else "" val mention = if (mentionAuthor && me?.username != username) "@$username" else ""
val room = if (roomTypeOf(roomType) is RoomType.DirectMessage) username else roomName val room =
if (roomTypeOf(roomType) is RoomType.DirectMessage) username else roomName
val chatRoomType = when (roomTypeOf(roomType)) { val chatRoomType = when (roomTypeOf(roomType)) {
is RoomType.DirectMessage -> "direct" is RoomType.DirectMessage -> "direct"
is RoomType.PrivateGroup -> "group" is RoomType.PrivateGroup -> "group"
...@@ -451,8 +503,12 @@ class ChatRoomPresenter @Inject constructor( ...@@ -451,8 +503,12 @@ class ChatRoomPresenter @Inject constructor(
view.showReplyingAction( view.showReplyingAction(
username = getDisplayName(msg.sender), username = getDisplayName(msg.sender),
replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ",
quotedMessage = mapper.map(message, RoomUiModel(roles = chatRoles, quotedMessage = mapper.map(
isBroadcast = chatIsBroadcast)).last().preview?.message ?: "" message, RoomUiModel(
roles = chatRoles,
isBroadcast = chatIsBroadcast
)
).last().preview?.message ?: ""
) )
} }
} }
...@@ -553,7 +609,12 @@ class ChatRoomPresenter @Inject constructor( ...@@ -553,7 +609,12 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
fun loadActiveMembers(chatRoomId: String, chatRoomType: String, offset: Long = 0, filterSelfOut: Boolean = false) { fun loadActiveMembers(
chatRoomId: String,
chatRoomType: String,
offset: Long = 0,
filterSelfOut: Boolean = false
) {
launchUI(strategy) { launchUI(strategy) {
try { try {
val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") { val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") {
...@@ -573,8 +634,12 @@ class ChatRoomPresenter @Inject constructor( ...@@ -573,8 +634,12 @@ class ChatRoomPresenter @Inject constructor(
val found = members.firstOrNull { member -> member.username == username } val found = members.firstOrNull { member -> member.username == username }
val status = if (found != null) found.status else UserStatus.Offline() val status = if (found != null) found.status else UserStatus.Offline()
val searchList = mutableListOf(username, name) val searchList = mutableListOf(username, name)
activeUsers.add(PeopleSuggestionUiModel(avatarUrl, username, username, name, status, activeUsers.add(
true, searchList)) PeopleSuggestionUiModel(
avatarUrl, username, username, name, status,
true, searchList
)
)
} }
// Filter out from members list the active users. // Filter out from members list the active users.
val others = members.filterNot { member -> val others = members.filterNot { member ->
...@@ -588,7 +653,15 @@ class ChatRoomPresenter @Inject constructor( ...@@ -588,7 +653,15 @@ class ChatRoomPresenter @Inject constructor(
val name = it.name ?: "" val name = it.name ?: ""
val avatarUrl = currentServer.avatarUrl(username) val avatarUrl = currentServer.avatarUrl(username)
val searchList = mutableListOf(username, name) val searchList = mutableListOf(username, name)
PeopleSuggestionUiModel(avatarUrl, username, username, name, it.status, true, searchList) PeopleSuggestionUiModel(
avatarUrl,
username,
username,
name,
it.status,
true,
searchList
)
}) })
view.populatePeopleSuggestions(activeUsers) view.populatePeopleSuggestions(activeUsers)
...@@ -613,8 +686,10 @@ class ChatRoomPresenter @Inject constructor( ...@@ -613,8 +686,10 @@ class ChatRoomPresenter @Inject constructor(
val name = it.name ?: "" val name = it.name ?: ""
val searchList = mutableListOf(username, name) val searchList = mutableListOf(username, name)
it.emails?.forEach { email -> searchList.add(email.address) } it.emails?.forEach { email -> searchList.add(email.address) }
PeopleSuggestionUiModel(currentServer.avatarUrl(username), PeopleSuggestionUiModel(
username, username, name, it.status, false, searchList) currentServer.avatarUrl(username),
username, username, name, it.status, false, searchList
)
}.filterNot { filterSelfOut && self != null && self == it.text }) }.filterNot { filterSelfOut && self != null && self == it.text })
} }
ROOMS -> { ROOMS -> {
...@@ -834,8 +909,11 @@ class ChatRoomPresenter @Inject constructor( ...@@ -834,8 +909,11 @@ class ChatRoomPresenter @Inject constructor(
private fun updateMessage(streamedMessage: Message) { private fun updateMessage(streamedMessage: Message) {
launchUI(strategy) { launchUI(strategy) {
val viewModelStreamedMessage = mapper.map(streamedMessage, RoomUiModel( val viewModelStreamedMessage = mapper.map(
roles = chatRoles, isBroadcast = chatIsBroadcast)) streamedMessage, RoomUiModel(
roles = chatRoles, isBroadcast = chatIsBroadcast
)
)
val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId) val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId)
val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id } val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id }
......
...@@ -8,11 +8,11 @@ import android.content.Context ...@@ -8,11 +8,11 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import androidx.fragment.app.Fragment
import android.view.View import android.view.View
import android.view.ViewAnimationUtils import android.view.ViewAnimationUtils
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import androidx.fragment.app.Fragment
fun View.rotateBy(value: Float, duration: Long = 100) { fun View.rotateBy(value: Float, duration: Long = 100) {
animate() animate()
...@@ -31,12 +31,12 @@ fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long ...@@ -31,12 +31,12 @@ fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long
.alpha(startValue) .alpha(startValue)
.setDuration(duration / 2) .setDuration(duration / 2)
.setInterpolator(DecelerateInterpolator()) .setInterpolator(DecelerateInterpolator())
.withEndAction({ .withEndAction {
animate() animate()
.alpha(finishValue) .alpha(finishValue)
.setDuration(duration / 2) .setDuration(duration / 2)
.setInterpolator(AccelerateInterpolator()).start() .setInterpolator(AccelerateInterpolator()).start()
}).start() }.start()
setVisible(true) setVisible(true)
} }
...@@ -51,18 +51,25 @@ fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long ...@@ -51,18 +51,25 @@ fun View.fadeOut(startValue: Float = 1f, finishValue: Float = 0f, duration: Long
.alpha(startValue) .alpha(startValue)
.setDuration(duration) .setDuration(duration)
.setInterpolator(DecelerateInterpolator()) .setInterpolator(DecelerateInterpolator())
.withEndAction({ .withEndAction {
animate() animate()
.alpha(finishValue) .alpha(finishValue)
.setDuration(duration) .setDuration(duration)
.setInterpolator(AccelerateInterpolator()).start() .setInterpolator(AccelerateInterpolator()).start()
}).start() }.start()
setVisible(false) setVisible(false)
} }
fun View.circularRevealOrUnreveal(centerX: Int, centerY: Int, startRadius: Float, endRadius: Float, duration: Long = 200) { fun View.circularRevealOrUnreveal(
val anim = ViewAnimationUtils.createCircularReveal(this, centerX, centerY, startRadius, endRadius) centerX: Int,
centerY: Int,
startRadius: Float,
endRadius: Float,
duration: Long = 200
) {
val anim =
ViewAnimationUtils.createCircularReveal(this, centerX, centerY, startRadius, endRadius)
anim.duration = duration anim.duration = duration
if (startRadius < endRadius) { if (startRadius < endRadius) {
...@@ -74,7 +81,7 @@ fun View.circularRevealOrUnreveal(centerX: Int, centerY: Int, startRadius: Float ...@@ -74,7 +81,7 @@ fun View.circularRevealOrUnreveal(centerX: Int, centerY: Int, startRadius: Float
anim.start() anim.start()
} }
fun View.shake(x: Float = 2F, num: Int = 0){ fun View.shake(x: Float = 2F, num: Int = 0) {
if (num == 6) { if (num == 6) {
this.translationX = 0.toFloat() this.translationX = 0.toFloat()
return return
...@@ -104,5 +111,4 @@ fun Fragment.vibrateSmartPhone() { ...@@ -104,5 +111,4 @@ fun Fragment.vibrateSmartPhone() {
} else { } else {
vibrator.vibrate(200) vibrator.vibrate(200)
} }
} }
\ No newline at end of file
package chat.rocket.android.util.extensions
import android.graphics.Bitmap
fun Bitmap.getCompressFormat(mimeType: String): Bitmap.CompressFormat {
return when {
mimeType.contains("jpeg") -> Bitmap.CompressFormat.JPEG
mimeType.contains("webp") -> Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.PNG
}
}
\ No newline at end of file
...@@ -2,17 +2,15 @@ package chat.rocket.android.util.extensions ...@@ -2,17 +2,15 @@ package chat.rocket.android.util.extensions
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
// TODO: Remove. Use KTX instead. // TODO: Remove. Use KTX instead.
...@@ -88,13 +86,4 @@ fun Fragment.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SH ...@@ -88,13 +86,4 @@ fun Fragment.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SH
showToast(getString(resource), duration) showToast(getString(resource), duration)
fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
activity?.showToast(message, duration) activity?.showToast(message, duration)
\ No newline at end of file
fun RecyclerView.isAtBottom(): Boolean {
val manager: RecyclerView.LayoutManager? = layoutManager
if (manager is LinearLayoutManager) {
return manager.findFirstVisibleItemPosition() == 0
}
return false // or true??? we can't determine the first visible item.
}
\ No newline at end of file
...@@ -3,6 +3,7 @@ package chat.rocket.android.util.extensions ...@@ -3,6 +3,7 @@ package chat.rocket.android.util.extensions
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.DocumentsContract import android.provider.DocumentsContract
...@@ -57,25 +58,17 @@ fun Uri.getMimeType(context: Context): String { ...@@ -57,25 +58,17 @@ fun Uri.getMimeType(context: Context): String {
} }
} }
fun Uri.getRealPathFromURI(context: Context): String? {
val cursor = context.contentResolver.query(this, arrayOf(MediaStore.Images.Media.DATA), null, null, null)
cursor.use { cursor ->
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
cursor.moveToFirst()
return cursor.getString(columnIndex)
}
}
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
fun Uri.isVirtualFile(context: Context): Boolean { fun Uri.isVirtualFile(context: Context): Boolean {
if (!DocumentsContract.isDocumentUri(context, this)) { if (!DocumentsContract.isDocumentUri(context, this)) {
return false return false
} }
val cursor = context.contentResolver.query(this, val cursor = context.contentResolver.query(
arrayOf(DocumentsContract.Document.COLUMN_FLAGS), this,
null, null, null) arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
null, null, null
)
var flags = 0 var flags = 0
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
...@@ -97,8 +90,8 @@ fun Uri.getInputStreamForVirtualFile(context: Context, mimeTypeFilter: String): ...@@ -97,8 +90,8 @@ fun Uri.getInputStreamForVirtualFile(context: Context, mimeTypeFilter: String):
throw FileNotFoundException() throw FileNotFoundException()
} }
return resolver.openTypedAssetFileDescriptor(this, openableMimeTypes[0], return resolver.openTypedAssetFileDescriptor(this, openableMimeTypes[0], null)
null)?.createInputStream() ?.createInputStream()
} }
fun Uri.getInputStream(context: Context): InputStream? { fun Uri.getInputStream(context: Context): InputStream? {
...@@ -107,4 +100,8 @@ fun Uri.getInputStream(context: Context): InputStream? { ...@@ -107,4 +100,8 @@ fun Uri.getInputStream(context: Context): InputStream? {
} }
return context.contentResolver.openInputStream(this) return context.contentResolver.openInputStream(this)
} }
\ No newline at end of file
fun Uri.getBitmpap(context: Context): Bitmap? {
return MediaStore.Images.Media.getBitmap(context.contentResolver, this)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment