Commit 6021c1f2 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Merge branch 'develop-2.x' into emoji-keyboard

parents 744f2f88 6c82cf20
...@@ -92,21 +92,19 @@ dependencies { ...@@ -92,21 +92,19 @@ dependencies {
implementation libraries.kotshiApi implementation libraries.kotshiApi
implementation libraries.frescoImageViewer implementation libraries.frescoImageViewer
implementation (libraries.androidSvg) {
implementation libraries.androidSvg exclude group: 'org.jetbrains', module: 'annotations-java5'
}
implementation libraries.aVLoadingIndicatorView
implementation libraries.textDrawable
implementation libraries.markwon implementation libraries.markwon
implementation (libraries.markwonImageLoader) {
implementation libraries.markwonImageLoader exclude group: 'com.caverock', module: 'androidsvg'
}
implementation libraries.moshiLazyAdapters
implementation libraries.sheetMenu implementation libraries.sheetMenu
implementation libraries.aVLoadingIndicatorView
implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') { implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true transitive = true
} }
......
import android.content.Context import android.content.Context
import android.graphics.Typeface
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v4.graphics.drawable.DrawableCompat import android.support.v4.graphics.drawable.DrawableCompat
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.TextHelper
import com.amulyakhare.textdrawable.TextDrawable
object DrawableHelper { object DrawableHelper {
private val AVATAR_BACKGROUND_HEXADECIMAL_COLORS = intArrayOf(
0xFFF44336.toInt(), 0xFFE91E63.toInt(), 0xFF9C27B0.toInt(), 0xFF673AB7.toInt(), 0xFF3F51B5.toInt(),
0xFF2196F3.toInt(), 0xFF03A9F4.toInt(), 0xFF00BCD4.toInt(), 0xFF009688.toInt(), 0xFF4CAF50.toInt(),
0xFF8BC34A.toInt(), 0xFFCDDC39.toInt(), 0xFFFFC107.toInt(), 0xFFFF9800.toInt(), 0xFFFF5722.toInt(),
0xFF795548.toInt(), 0xFF9E9E9E.toInt(), 0xFF607D8B.toInt())
/** /**
* Returns a Drawable from its ID. * Returns a Drawable from its ID.
* *
...@@ -123,29 +116,4 @@ object DrawableHelper { ...@@ -123,29 +116,4 @@ object DrawableHelper {
} }
return userStatusDrawable return userStatusDrawable
} }
/**
* Returns a drawable with the first character from a string.
*
* @param string The string to get its first character and to get the avatar background color.
* @return A drawable with the string first character.
*/
fun getTextDrawable(string: String): Drawable {
return TextDrawable.builder()
.beginConfig()
.useFont(Typeface.SANS_SERIF)
.endConfig()
.buildRoundRect(TextHelper.getFirstCharacter(string), getAvatarBackgroundColor(string), 4)
}
/**
* Returns a background color to be rendered on the avatar.
*
* @param string Gets the background color based on the provided string.
* @return A hexadecimal color.
* @see (Rocket.Chat/server/startup/avatar.js)
*/
private fun getAvatarBackgroundColor(string: String): Int {
return AVATAR_BACKGROUND_HEXADECIMAL_COLORS[string.length % AVATAR_BACKGROUND_HEXADECIMAL_COLORS.size]
}
} }
\ No newline at end of file
...@@ -23,8 +23,8 @@ import com.facebook.imagepipeline.image.QualityInfo ...@@ -23,8 +23,8 @@ import com.facebook.imagepipeline.image.QualityInfo
object SvgDecoder { object SvgDecoder {
val svgFormat = ImageFormat("SVG_FORMAT", "svg") val svgFormat = ImageFormat("SVG_FORMAT", "svg")
// We do not include the closing ">" since there can be additional information. // We do not include the closing ">" since there can be additional information.
private val headerTag = "<?xml" private val headerTag = "<svg"
private val possibleHeaderTags = arrayOf(ImageFormatCheckerUtils.asciiBytes("<svg")) private val possibleHeaderTags = arrayOf(ImageFormatCheckerUtils.asciiBytes("<?xml"))
/** /**
* Custom SVG format checker that verifies that the header of the file corresponds to our [SvgDecoder.headerTag] or [SvgDecoder.possibleHeaderTags]. * Custom SVG format checker that verifies that the header of the file corresponds to our [SvgDecoder.headerTag] or [SvgDecoder.possibleHeaderTags].
......
...@@ -4,66 +4,41 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator ...@@ -4,66 +4,41 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.settings
import java.security.InvalidParameterException
import javax.inject.Inject import javax.inject.Inject
class ServerPresenter @Inject constructor(private val view: ServerView, class ServerPresenter @Inject constructor(private val view: ServerView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val serverInteractor: SaveCurrentServerInteractor, private val serverInteractor: SaveCurrentServerInteractor,
private val settingsInteractor: SaveSettingsInteractor, private val refreshSettingsInteractor: RefreshSettingsInteractor) {
private val factory: RocketChatClientFactory) {
private lateinit var client: RocketChatClient
private var settingsFilter = arrayOf(SITE_URL, SITE_NAME, FAVICON_512, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS, FAVORITE_ROOMS,
ACCOUNT_LOGIN_FORM, ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_GITLAB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR,
ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, LDAP_ENABLE, ACCOUNT_REGISTRATION, STORAGE_TYPE, HIDE_USER_JOIN, HIDE_USER_LEAVE, HIDE_TYPE_AU,
HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ACCOUNT_CUSTOM_FIELDS, ALLOW_MESSAGE_DELETING, ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING,
SHOW_DELETED_STATUS, SHOW_EDITED_STATUS)
fun connect(server: String) { fun connect(server: String) {
if (!UrlHelper.isValidUrl(server)) { if (!UrlHelper.isValidUrl(server)) {
view.showInvalidServerUrl() view.showInvalidServerUrl()
} else { } else {
try { launchUI(strategy) {
client = factory.create(server) if (NetworkHelper.hasInternetAccess()) {
} catch (exception: InvalidParameterException) { view.showLoading()
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
return
}
client.let { rocketChatClient ->
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
try { try {
val settings = rocketChatClient.settings(*settingsFilter) refreshSettingsInteractor.refresh(server)
settingsInteractor.save(server, settings) serverInteractor.save(server)
serverInteractor.save(server)
navigator.toLogin() navigator.toLogin()
} catch (exception: Exception) { } catch (ex: Exception) {
exception.message?.let { ex.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
} }
} else { } finally {
view.showNoInternetConnection() view.hideLoading()
} }
} else {
view.showNoInternetConnection()
} }
} }
} }
......
...@@ -2,11 +2,7 @@ package chat.rocket.android.chatroom.domain ...@@ -2,11 +2,7 @@ package chat.rocket.android.chatroom.domain
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import chat.rocket.android.util.extensions.getFileName import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.extensions.getMimeType
import chat.rocket.android.util.extensions.getRealPathFromURI
import okio.Okio
import java.io.File
import javax.inject.Inject import javax.inject.Inject
...@@ -28,26 +24,7 @@ class UriInteractor @Inject constructor(private val context: Context) { ...@@ -28,26 +24,7 @@ class UriInteractor @Inject constructor(private val context: Context) {
*/ */
fun getRealPath(uri: Uri): String? = uri.getRealPathFromURI(context) fun getRealPath(uri: Uri): String? = uri.getRealPathFromURI(context)
/** fun getFileSize(uri: Uri) = uri.getFileSize(context)
* Save the contents of an [Uri] to a temp file named after uri.getFileName()
*/ fun getInputStream(uri: Uri) = uri.getInputStream(context)
fun tempFile(uri: Uri): File? {
try {
val outputDir = context.cacheDir // context being the Activity pointer
val outputFile = File(outputDir, uri.getFileName(context))
val from = context.contentResolver.openInputStream(uri)
Okio.source(from).use { a ->
Okio.buffer(Okio.sink(outputFile)).use{ b ->
b.writeAll(a)
b.close()
}
a.close()
}
return outputFile
} catch (ex: Exception) {
ex.printStackTrace()
return null
}
}
} }
\ No newline at end of file
...@@ -24,7 +24,6 @@ import kotlinx.coroutines.experimental.async ...@@ -24,7 +24,6 @@ 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 timber.log.Timber import timber.log.Timber
import java.io.File
import javax.inject.Inject import javax.inject.Inject
class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
...@@ -39,7 +38,6 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -39,7 +38,6 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val client = factory.create(serverInteractor.get()!!) private val client = factory.create(serverInteractor.get()!!)
private var subId: String? = null private var subId: String? = null
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)!! private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)!!
private val stateChannel = Channel<State>() private val stateChannel = Channel<State>()
fun loadMessages(chatRoomId: String, chatRoomType: String, offset: Long = 0) { fun loadMessages(chatRoomId: String, chatRoomType: String, offset: Long = 0) {
...@@ -72,53 +70,49 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -72,53 +70,49 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
fun sendMessage(chatRoomId: String, text: String, messageId: String?) { fun sendMessage(chatRoomId: String, text: String, messageId: String?) {
launchUI(strategy) { launchUI(strategy) {
view.disableMessageInput() view.disableSendMessageButton()
try { try {
// ignore message for now, will receive it on the stream
val message = if (messageId == null) { val message = if (messageId == null) {
client.sendMessage(chatRoomId, text) client.sendMessage(chatRoomId, text)
} else { } else {
client.updateMessage(chatRoomId, messageId, text) client.updateMessage(chatRoomId, messageId, text)
} }
// ignore message for now, will receive it on the stream view.clearMessageComposition()
view.enableMessageInput(clear = true)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace()
ex.message?.let { ex.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
view.enableMessageInput() } finally {
view.enableSendMessageButton()
} }
} }
} }
fun selectFile() {
view.showFileSelection(settings.uploadMimeTypeFilter())
}
fun uploadFile(roomId: String, uri: Uri, msg: String) { fun uploadFile(roomId: String, uri: Uri, msg: String) {
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
var tempFile: File? = null
try { try {
val fileName = async { uriInteractor.getFileName(uri) }.await() val fileName = async { uriInteractor.getFileName(uri) }.await()
val mimeType = async { uriInteractor.getMimeType(uri) }.await() val mimeType = async { uriInteractor.getMimeType(uri) }.await()
/* FIXME - this is a workaround for uploading files with the SDK val fileSize = async { uriInteractor.getFileSize(uri) }.await()
* val maxFileSize = settings.uploadMaxFileSize()
* https://developer.android.com/guide/topics/providers/document-provider.html
*
* We need to use contentResolver.openInputStream(uri) to open this file.
* Since the SDK is not Android specific we cannot pass the Uri and let the
* SDK handle the file.
*
* As a temporary workaround we are saving the contents to a temp file.
*
* A proper solution is to implement some interface to open the InputStream
* and use a RequestBody based on https://github.com/square/okhttp/issues/3585
*/
tempFile = async { uriInteractor.tempFile(uri) }.await()
if (fileName == null || tempFile == null) { when {
view.showInvalidFileMessage() fileName.isNullOrEmpty() -> view.showInvalidFileMessage()
} else { fileSize > maxFileSize -> view.showInvalidFileSize(fileSize, maxFileSize)
client.uploadFile(roomId, tempFile, mimeType, msg, fileName) else -> {
Timber.d("Uploading to $roomId: $fileName - $mimeType")
client.uploadFile(roomId, fileName!!, mimeType, msg, description = fileName) {
uriInteractor.getInputStream(uri)
}
}
} }
} catch (ex: RocketChatException) { } catch (ex: RocketChatException) {
Timber.d(ex) Timber.d(ex)
...@@ -128,13 +122,12 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -128,13 +122,12 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} finally { } finally {
tempFile?.delete()
view.hideLoading() view.hideLoading()
} }
} }
} }
fun subscribeMessages(roomId: String) { private fun subscribeMessages(roomId: String) {
client.addStateChannel(stateChannel) client.addStateChannel(stateChannel)
launch(CommonPool + strategy.jobs) { launch(CommonPool + strategy.jobs) {
for (status in stateChannel) { for (status in stateChannel) {
...@@ -336,4 +329,4 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -336,4 +329,4 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
} }
} }
} }
} }
\ No newline at end of file
...@@ -21,6 +21,11 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -21,6 +21,11 @@ interface ChatRoomView : LoadingView, MessageView {
*/ */
fun sendMessage(text: String) fun sendMessage(text: String)
/**
* Perform file selection with the mime type [filter]
*/
fun showFileSelection(filter: Array<String>)
/** /**
* Uploads a file to a chat room. * Uploads a file to a chat room.
* *
...@@ -75,7 +80,21 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -75,7 +80,21 @@ interface ChatRoomView : LoadingView, MessageView {
*/ */
fun showEditingAction(roomId: String, messageId: String, text: String) fun showEditingAction(roomId: String, messageId: String, text: String)
fun disableMessageInput() /**
* Disabling the send message button avoids the user tap this button multiple
* times to send a same message.
*/
fun disableSendMessageButton()
/**
* Enables the send message button.
*/
fun enableSendMessageButton()
/**
* Clears the message composition.
*/
fun clearMessageComposition()
fun enableMessageInput(clear: Boolean = false) fun showInvalidFileSize(fileSize: Int, maxFileSize: Int)
} }
\ No newline at end of file
...@@ -27,9 +27,12 @@ import chat.rocket.android.widget.emoji.Emoji ...@@ -27,9 +27,12 @@ import chat.rocket.android.widget.emoji.Emoji
import chat.rocket.android.widget.emoji.EmojiFragment import chat.rocket.android.widget.emoji.EmojiFragment
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.fragment_chat_room.* import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.* import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.* import kotlinx.android.synthetic.main.message_composer.*
import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
fun newInstance(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean): Fragment { fun newInstance(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean): Fragment {
...@@ -63,12 +66,15 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -63,12 +66,15 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
private var citation: String? = null private var citation: String? = null
private var editingMessageId: String? = null private var editingMessageId: String? = null
private val compositeDisposable = CompositeDisposable()
private var playComposeMessageButtonsAnimation = true
// For reveal and unreveal anim. // For reveal and unreveal anim.
private val hypotenuse by lazy { Math.hypot(root_layout.width.toDouble(), root_layout.height.toDouble()).toFloat() } private val hypotenuse by lazy { Math.hypot(root_layout.width.toDouble(), root_layout.height.toDouble()).toFloat() }
private val max by lazy { Math.max(layout_message_attachment_options.width.toDouble(), layout_message_attachment_options.height.toDouble()).toFloat() } private val max by lazy { Math.max(layout_message_attachment_options.width.toDouble(), layout_message_attachment_options.height.toDouble()).toFloat() }
private val centerX by lazy { recycler_view.right } private val centerX by lazy { recycler_view.right }
private val centerY by lazy { recycler_view.bottom } private val centerY by lazy { recycler_view.bottom }
val handler = Handler() private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -91,7 +97,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -91,7 +97,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
presenter.loadMessages(chatRoomId, chatRoomType) presenter.loadMessages(chatRoomId, chatRoomType)
setupComposer()
setupRecyclerView()
setupFab()
setupMessageComposer()
setupActionSnackbar() setupActionSnackbar()
} }
...@@ -105,6 +114,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -105,6 +114,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
override fun onDestroyView() { override fun onDestroyView() {
presenter.unsubscribeMessages() presenter.unsubscribeMessages()
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
unsubscribeTextMessage()
super.onDestroyView() super.onDestroyView()
} }
...@@ -179,18 +189,24 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -179,18 +189,24 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
override fun showNewMessage(message: MessageViewModel) { override fun showNewMessage(message: MessageViewModel) {
adapter.addItem(message) adapter.addItem(message)
recycler_view.smoothScrollToPosition(0) recycler_view.scrollToPosition(0)
} }
override fun disableMessageInput() { override fun disableSendMessageButton() {
button_send.isEnabled = false button_send.isEnabled = false
text_message.isEnabled = false
} }
override fun enableMessageInput(clear: Boolean) { override fun enableSendMessageButton() {
button_send.isEnabled = true button_send.isEnabled = true
text_message.isEnabled = true text_message.isEnabled = true
if (clear) text_message.erase() text_message.erase()
}
override fun clearMessageComposition() {
citation = null
editingMessageId = null
text_message.textContent = ""
actionSnackbar.dismiss()
} }
override fun dispatchUpdateMessage(index: Int, message: MessageViewModel) { override fun dispatchUpdateMessage(index: Int, message: MessageViewModel) {
...@@ -267,26 +283,47 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -267,26 +283,47 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
} }
} }
private fun setupComposer() { override fun showFileSelection(filter: Array<String>) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, filter)
startActivityForResult(intent, REQUEST_CODE_FOR_PERFORM_SAF)
}
override fun showInvalidFileSize(fileSize: Int, maxFileSize: Int) {
showMessage(getString(R.string.max_file_size_exceeded, fileSize, maxFileSize))
}
private fun setupRecyclerView() {
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
Timber.i("Scrolling vertically: $dy")
if (!recyclerView.canScrollVertically(1)) {
button_fab.hide()
} else {
if (dy > 0 && !button_fab.isVisible()) {
button_fab.show()
} else if (dy < 0 && button_fab.isVisible()) {
button_fab.hide()
}
}
}
})
}
private fun setupFab() {
button_fab.setOnClickListener {
recycler_view.scrollToPosition(0)
button_fab.hide()
}
}
private fun setupMessageComposer() {
if (isChatRoomReadOnly) { if (isChatRoomReadOnly) {
text_room_is_read_only.setVisible(true) text_room_is_read_only.setVisible(true)
input_container.setVisible(false) input_container.setVisible(false)
} else { } else {
var playAnimation = true subscribeTextMessage()
text_message.asObservable(0)
.subscribe({ t ->
if (t.isNotEmpty() && playAnimation) {
button_show_attachment_options.fadeInOrOut(1F, 0F, 120)
button_send.fadeInOrOut(0F, 1F, 120)
playAnimation = false
}
if (t.isEmpty()) {
button_send.fadeInOrOut(1F, 0F, 120)
button_show_attachment_options.fadeInOrOut(0F, 1F, 120)
playAnimation = true
}
})
text_message.listener = object : ComposerEditText.ComposerEditTextListener { text_message.listener = object : ComposerEditText.ComposerEditTextListener {
override fun onKeyboardOpened() { override fun onKeyboardOpened() {
...@@ -321,10 +358,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -321,10 +358,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
it.hide() it.hide()
} }
} }
clearActionMessage() clearMessageComposition()
} }
button_show_attachment_options.setOnClickListener { button_show_attachment_options.setOnClickListener {
if (layout_message_attachment_options.isShown) { if (layout_message_attachment_options.isShown) {
hideAttachmentOptions() hideAttachmentOptions()
...@@ -334,11 +370,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -334,11 +370,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
} }
} }
view_dim.setOnClickListener { hideAttachmentOptions() } view_dim.setOnClickListener {
hideAttachmentOptions()
}
button_files.setOnClickListener { button_files.setOnClickListener {
handler.postDelayed({ handler.postDelayed({
performSAF() presenter.selectFile()
}, 300) }, 300)
handler.postDelayed({ handler.postDelayed({
...@@ -380,15 +418,35 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -380,15 +418,35 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
private fun setupActionSnackbar() { private fun setupActionSnackbar() {
actionSnackbar = ActionSnackbar.make(message_list_container, parser = parser) actionSnackbar = ActionSnackbar.make(message_list_container, parser = parser)
actionSnackbar.cancelView.setOnClickListener({ actionSnackbar.cancelView.setOnClickListener({
clearActionMessage() clearMessageComposition()
}) })
} }
private fun clearActionMessage() { private fun subscribeTextMessage() {
citation = null val disposable = text_message.asObservable(0)
editingMessageId = null .subscribe({ t -> setupComposeMessageButtons(t) })
text_message.text.clear()
actionSnackbar.dismiss() compositeDisposable.add(disposable)
}
private fun unsubscribeTextMessage() {
if (!compositeDisposable.isDisposed) {
compositeDisposable.dispose()
}
}
private fun setupComposeMessageButtons(charSequence: CharSequence) {
if (charSequence.isNotEmpty() && playComposeMessageButtonsAnimation) {
button_show_attachment_options.fadeOut(1F, 0F, 120)
button_send.fadeIn(0F, 1F, 120)
playComposeMessageButtonsAnimation = false
}
if (charSequence.isEmpty()) {
button_send.fadeOut(1F, 0F, 120)
button_show_attachment_options.fadeIn(0F, 1F, 120)
playComposeMessageButtonsAnimation = true
}
} }
private fun showAttachmentOptions() { private fun showAttachmentOptions() {
...@@ -406,10 +464,4 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi ...@@ -406,10 +464,4 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardLi
view_dim.setVisible(false) view_dim.setVisible(false)
} }
private fun performSAF() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
startActivityForResult(intent, REQUEST_CODE_FOR_PERFORM_SAF)
}
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -4,6 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetChatRoomsInteractor import chat.rocket.android.server.domain.GetChatRoomsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveChatRoomsInteractor import chat.rocket.android.server.domain.SaveChatRoomsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
...@@ -28,6 +29,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -28,6 +29,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val getChatRoomsInteractor: GetChatRoomsInteractor, private val getChatRoomsInteractor: GetChatRoomsInteractor,
private val saveChatRoomsInteractor: SaveChatRoomsInteractor, private val saveChatRoomsInteractor: SaveChatRoomsInteractor,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory) {
private val client: RocketChatClient = factory.create(serverInteractor.get()!!) private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
...@@ -36,6 +38,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -36,6 +38,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val stateChannel = Channel<State>() private val stateChannel = Channel<State>()
fun loadChatRooms() { fun loadChatRooms() {
refreshSettingsInteractor.refreshAsync(currentServer)
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
...@@ -232,7 +235,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -232,7 +235,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
subscription.readonly ?: readonly, subscription.readonly ?: readonly,
subscription.updatedAt ?: updatedAt, subscription.updatedAt ?: updatedAt,
subscription.timestamp ?: timestamp, subscription.timestamp ?: timestamp,
subscription.lastModified ?: lastSeen, subscription.lastSeen ?: lastSeen,
topic, topic,
announcement, announcement,
subscription.isDefault, subscription.isDefault,
......
package chat.rocket.android.chatrooms.ui package chat.rocket.android.chatrooms.ui
import DateTimeHelper import DateTimeHelper
import DrawableHelper
import android.content.Context import android.content.Context
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.common.model.RoomType
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_chat.view.* import kotlinx.android.synthetic.main.item_chat.view.*
import kotlinx.android.synthetic.main.unread_messages_badge.view.*
class ChatRoomsAdapter(private val context: Context, class ChatRoomsAdapter(private val context: Context,
private val listener: (ChatRoom) -> Unit) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() { private val listener: (ChatRoom) -> Unit) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
...@@ -39,7 +37,7 @@ class ChatRoomsAdapter(private val context: Context, ...@@ -39,7 +37,7 @@ class ChatRoomsAdapter(private val context: Context,
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(chatRoom: ChatRoom) = with(itemView) { fun bind(chatRoom: ChatRoom) = with(itemView) {
bindAvatar(chatRoom, layout_avatar, image_avatar, image_room_avatar) bindAvatar(chatRoom, image_avatar)
bindName(chatRoom, text_chat_name) bindName(chatRoom, text_chat_name)
bindLastMessageDateTime(chatRoom, text_last_message_date_time) bindLastMessageDateTime(chatRoom, text_last_message_date_time)
bindLastMessage(chatRoom, text_last_message) bindLastMessage(chatRoom, text_last_message)
...@@ -48,17 +46,8 @@ class ChatRoomsAdapter(private val context: Context, ...@@ -48,17 +46,8 @@ class ChatRoomsAdapter(private val context: Context,
setOnClickListener { listener(chatRoom) } setOnClickListener { listener(chatRoom) }
} }
private fun bindAvatar(chatRoom: ChatRoom, avatarLayout: View, drawee: SimpleDraweeView, imageView: ImageView) { private fun bindAvatar(chatRoom: ChatRoom, drawee: SimpleDraweeView) {
val chatRoomName = chatRoom.name drawee.setImageURI(UrlHelper.getAvatarUrl(chatRoom.client.url, chatRoom.name))
if (chatRoom.type is RoomType.DirectMessage) {
drawee.setImageURI(UrlHelper.getAvatarUrl(chatRoom.client.url, chatRoomName))
imageView.setVisible(false)
avatarLayout.setVisible(true)
} else {
imageView.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName))
avatarLayout.setVisible(false)
imageView.setVisible(true)
}
} }
private fun bindName(chatRoom: ChatRoom, textView: TextView) { private fun bindName(chatRoom: ChatRoom, textView: TextView) {
......
...@@ -3,10 +3,9 @@ package chat.rocket.android.helper ...@@ -3,10 +3,9 @@ package chat.rocket.android.helper
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import timber.log.Timber import timber.log.Timber
class CrashlyticsTree : Timber.Tree() { class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String?, throwable: Throwable?) { override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {
Crashlytics.log(priority, tag, message) Crashlytics.log(priority, tag, message)
if (throwable != null) { if (throwable != null) {
......
package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.core.internal.rest.settings
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.withContext
import javax.inject.Inject
class RefreshSettingsInteractor @Inject constructor(private val factory: RocketChatClientFactory,
private val repository: SettingsRepository) {
private var settingsFilter = arrayOf(
SITE_URL, SITE_NAME, FAVICON_512, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS,
FAVORITE_ROOMS, ACCOUNT_LOGIN_FORM, ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB,
ACCOUNT_GITLAB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR, ACCOUNT_TWITTER, ACCOUNT_WORDPRESS,
LDAP_ENABLE, ACCOUNT_REGISTRATION, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE,
UPLOAD_WHITELIST_MIMETYPES, HIDE_USER_JOIN, HIDE_USER_LEAVE, HIDE_TYPE_AU, HIDE_MUTE_UNMUTE,
HIDE_TYPE_RU, ACCOUNT_CUSTOM_FIELDS, ALLOW_MESSAGE_DELETING, ALLOW_MESSAGE_EDITING,
ALLOW_MESSAGE_PINNING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS)
suspend fun refresh(server: String) {
withContext(CommonPool) {
factory.create(server).let { client ->
val settings = client.settings(*settingsFilter)
repository.save(server, settings)
}
}
}
fun refreshAsync(server: String) {
async {
try {
refresh(server)
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
}
\ No newline at end of file
package chat.rocket.android.server.domain package chat.rocket.android.server.domain
import chat.rocket.android.util.extensions.mapToTypedArray
import chat.rocket.core.model.Value import chat.rocket.core.model.Value
typealias PublicSettings = Map<String, Value<Any>> typealias PublicSettings = Map<String, Value<Any>>
...@@ -28,7 +29,9 @@ const val USE_REALNAME = "UI_Use_Real_Name" ...@@ -28,7 +29,9 @@ const val USE_REALNAME = "UI_Use_Real_Name"
const val ALLOW_ROOM_NAME_SPECIAL_CHARS = "UI_Allow_room_names_with_special_chars" const val ALLOW_ROOM_NAME_SPECIAL_CHARS = "UI_Allow_room_names_with_special_chars"
const val FAVORITE_ROOMS = "Favorite_Rooms" const val FAVORITE_ROOMS = "Favorite_Rooms"
const val LDAP_ENABLE = "LDAP_Enable" const val LDAP_ENABLE = "LDAP_Enable"
const val STORAGE_TYPE = "FileUpload_Storage_Type" const val UPLOAD_STORAGE_TYPE = "FileUpload_Storage_Type"
const val UPLOAD_MAX_FILE_SIZE = "FileUpload_MaxFileSize"
const val UPLOAD_WHITELIST_MIMETYPES = "FileUpload_MediaTypeWhiteList"
const val HIDE_USER_JOIN = "Message_HideType_uj" const val HIDE_USER_JOIN = "Message_HideType_uj"
const val HIDE_USER_LEAVE = "Message_HideType_ul" const val HIDE_USER_LEAVE = "Message_HideType_ul"
const val HIDE_TYPE_AU = "Message_HideType_au" const val HIDE_TYPE_AU = "Message_HideType_au"
...@@ -66,4 +69,17 @@ fun Map<String, Value<Any>>.allowedMessageDeleting(): Boolean = this[ALLOW_MESSA ...@@ -66,4 +69,17 @@ fun Map<String, Value<Any>>.allowedMessageDeleting(): Boolean = this[ALLOW_MESSA
fun Map<String, Value<Any>>.registrationEnabled(): Boolean { fun Map<String, Value<Any>>.registrationEnabled(): Boolean {
val value = this[ACCOUNT_REGISTRATION] val value = this[ACCOUNT_REGISTRATION]
return value?.value == "Public" return value?.value == "Public"
}
fun Map<String, Value<Any>>.uploadMimeTypeFilter(): Array<String> {
val values = this[UPLOAD_WHITELIST_MIMETYPES]?.value
values?.let { it as String }?.split(",")?.let {
return it.mapToTypedArray { it.trim() }
}
return arrayOf("*/*")
}
fun Map<String, Value<Any>>.uploadMaxFileSize(): Int {
return this[UPLOAD_MAX_FILE_SIZE]?.value?.let { it as Int } ?: Int.MAX_VALUE
} }
\ No newline at end of file
...@@ -12,7 +12,7 @@ fun View.rotateBy(value: Float, duration: Long = 200) { ...@@ -12,7 +12,7 @@ fun View.rotateBy(value: Float, duration: Long = 200) {
.start() .start()
} }
fun View.fadeInOrOut(startValue: Float, finishValue: Float, duration: Long = 200) { fun View.fadeIn(startValue: Float, finishValue: Float, duration: Long = 200) {
animate() animate()
.alpha(startValue) .alpha(startValue)
.setDuration(duration) .setDuration(duration)
...@@ -24,11 +24,22 @@ fun View.fadeInOrOut(startValue: Float, finishValue: Float, duration: Long = 200 ...@@ -24,11 +24,22 @@ fun View.fadeInOrOut(startValue: Float, finishValue: Float, duration: Long = 200
.setInterpolator(AccelerateInterpolator()).start() .setInterpolator(AccelerateInterpolator()).start()
}).start() }).start()
if (startValue > finishValue) { setVisible(true)
setVisible(false) }
} else {
setVisible(true) fun View.fadeOut(startValue: Float, finishValue: Float, duration: Long = 200) {
} animate()
.alpha(startValue)
.setDuration(duration)
.setInterpolator(DecelerateInterpolator())
.withEndAction({
animate()
.alpha(finishValue)
.setDuration(duration)
.setInterpolator(AccelerateInterpolator()).start()
}).start()
setVisible(false)
} }
fun View.circularRevealOrUnreveal(centerX: Int, centerY: Int, startRadius: Float, endRadius: Float, duration: Long = 600) { fun View.circularRevealOrUnreveal(centerX: Int, centerY: Int, startRadius: Float, endRadius: Float, duration: Long = 600) {
......
package chat.rocket.android.util.extensions
inline fun <T, reified R> List<T>.mapToTypedArray(transform: (T) -> R): Array<R> {
return when (this) {
is RandomAccess -> Array(size) { index -> transform(this[index]) }
else -> with(iterator()) { Array(size) { transform(next()) } }
}
}
\ No newline at end of file
package chat.rocket.android.util.extensions package chat.rocket.android.util.extensions
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.widget.TextView
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import android.provider.MediaStore
import android.text.Spannable import android.text.Spannable
import android.text.Spanned import android.text.Spanned
import android.text.TextUtils import android.text.TextUtils
import android.widget.EditText import android.widget.EditText
import android.widget.TextView
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.android.widget.emoji.EmojiTypefaceSpan import chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
...@@ -65,50 +59,4 @@ var TextView.content: CharSequence ...@@ -65,50 +59,4 @@ var TextView.content: CharSequence
} }
Markwon.scheduleDrawables(this) Markwon.scheduleDrawables(this)
Markwon.scheduleTableRows(this) Markwon.scheduleTableRows(this)
} }
\ No newline at end of file
fun Uri.getFileName(context: Context): String? {
val cursor = context.contentResolver.query(this, null, null, null, null, null)
var fileName: String? = null
cursor.use { cursor ->
if (cursor != null && cursor.moveToFirst()) {
fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
}
return fileName
}
fun Uri.getFileSize(context: Context): String? {
val cursor = context.contentResolver.query(this, null, null, null, null, null)
var fileSize: String? = null
cursor.use { cursor ->
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
if (cursor != null && cursor.moveToFirst()) {
if (!cursor.isNull(sizeIndex)) {
fileSize = cursor.getString(sizeIndex)
}
}
}
return fileSize
}
fun Uri.getMimeType(context: Context): String {
return if (scheme == ContentResolver.SCHEME_CONTENT) {
context.contentResolver.getType(this)
} else {
val fileExtension = MimeTypeMap.getFileExtensionFromUrl(toString())
MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase())
}
}
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)
}
}
\ No newline at end of file
...@@ -21,6 +21,10 @@ fun View.setVisible(visible: Boolean) { ...@@ -21,6 +21,10 @@ fun View.setVisible(visible: Boolean) {
} }
} }
fun View.isVisible(): Boolean {
return visibility == View.VISIBLE
}
fun ViewGroup.inflate(@LayoutRes resource: Int): View = LayoutInflater.from(context).inflate(resource, this, false) fun ViewGroup.inflate(@LayoutRes resource: Int): View = LayoutInflater.from(context).inflate(resource, this, false)
fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) { fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) {
......
package chat.rocket.android.util.extensions
import android.annotation.TargetApi
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
fun Uri.getFileName(context: Context): String? {
val cursor = context.contentResolver.query(this, null, null, null, null, null)
var fileName: String? = null
cursor.use { cursor ->
if (cursor != null && cursor.moveToFirst()) {
fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
}
return fileName
}
fun Uri.getFileSize(context: Context): Int {
val cursor = context.contentResolver.query(this, null, null, null, null, null)
var fileSize: String? = null
cursor.use { cursor ->
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
if (cursor != null && cursor.moveToFirst()) {
if (!cursor.isNull(sizeIndex)) {
fileSize = cursor.getString(sizeIndex)
}
}
}
return fileSize?.toIntOrNull() ?: -1
}
fun Uri.getMimeType(context: Context): String {
return if (scheme == ContentResolver.SCHEME_CONTENT) {
context.contentResolver.getType(this)
} else {
val fileExtension = MimeTypeMap.getFileExtensionFromUrl(toString())
MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase())
}
}
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)
fun Uri.isVirtualFile(context: Context): Boolean {
if (!DocumentsContract.isDocumentUri(context, this)) {
return false
}
val cursor = context.contentResolver.query(this,
arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
null, null, null)
var flags = 0
if (cursor.moveToFirst()) {
flags = cursor.getInt(0)
}
cursor.close()
return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0
}
@Throws(IOException::class)
fun Uri.getInputStreamForVirtualFile(context: Context, mimeTypeFilter: String): FileInputStream? {
val resolver = context.contentResolver
val openableMimeTypes = resolver.getStreamTypes(this, mimeTypeFilter)
if (openableMimeTypes == null || openableMimeTypes.isEmpty()) {
throw FileNotFoundException()
}
return resolver.openTypedAssetFileDescriptor(this, openableMimeTypes[0],
null)?.createInputStream()
}
fun Uri.getInputStream(context: Context): InputStream? {
if (isVirtualFile(context)) {
return getInputStreamForVirtualFile(context, "*/*")
}
return context.contentResolver.openInputStream(this)
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF010101"
android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z" />
</vector>
\ No newline at end of file
...@@ -9,6 +9,6 @@ ...@@ -9,6 +9,6 @@
android:id="@+id/image_avatar" android:id="@+id/image_avatar"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
app:roundedCornerRadius="2dp" /> app:roundedCornerRadius="3dp" />
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout" android:id="@+id/root_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="vertical">
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
...@@ -20,16 +19,23 @@ ...@@ -20,16 +19,23 @@
<FrameLayout <FrameLayout
android:id="@+id/message_list_container" android:id="@+id/message_list_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_above="@+id/layout_message_composer"> android:layout_above="@+id/layout_message_composer">
<android.support.v7.widget.RecyclerView <include
android:id="@+id/recycler_view" android:id="@+id/layout_message_list"
layout="@layout/message_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
android:scrollbars="vertical" />
</FrameLayout> </FrameLayout>
<include
android:id="@+id/layout_message_composer"
layout="@layout/message_composer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
<View <View
android:id="@+id/view_dim" android:id="@+id/view_dim"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -45,14 +51,6 @@ ...@@ -45,14 +51,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@+id/layout_message_composer" android:layout_above="@+id/layout_message_composer"
android:layout_margin="5dp" android:layout_margin="5dp"
android:visibility="gone" android:visibility="gone" />
tools:visibility="visible" />
<include
android:id="@+id/layout_message_composer"
layout="@layout/message_composer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
</RelativeLayout> </RelativeLayout>
...@@ -9,33 +9,15 @@ ...@@ -9,33 +9,15 @@
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins" android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="12dp"> android:layout_marginTop="12dp">
<android.support.constraint.ConstraintLayout <include
android:id="@+id/avatar_container" android:id="@+id/layout_avatar"
android:layout_width="wrap_content" layout="@layout/avatar"
android:layout_height="wrap_content" android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/middle_container" app:layout_constraintRight_toLeftOf="@+id/middle_container"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/layout_avatar"
layout="@layout/avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone" />
<!-- We need to build the avatar with initials since the server returns a SVG file with a pre defined 50x50 pixel.
TODO: check to scale the SVG file on the app and remove the view bellow (or make the server to return a jpg file.
SVG scaling with fresco is impossible: http://frescolib.org/docs/resizing.html
-->
<ImageView
android:id="@+id/image_room_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone"
tools:ignore="contentDescription" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout
android:id="@+id/middle_container" android:id="@+id/middle_container"
...@@ -43,7 +25,7 @@ ...@@ -43,7 +25,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
app:layout_constraintLeft_toRightOf="@+id/avatar_container" app:layout_constraintLeft_toRightOf="@+id/layout_avatar"
app:layout_constraintRight_toLeftOf="@+id/right_container"> app:layout_constraintRight_toLeftOf="@+id/right_container">
<TextView <TextView
...@@ -78,23 +60,16 @@ ...@@ -78,23 +60,16 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/guideline_one" app:layout_constraintBottom_toTopOf="@+id/guideline_one"
app:layout_constraintRight_toRightOf="@+id/text_total_unread_messages" app:layout_constraintRight_toRightOf="@+id/layout_unread_messages_badge"
tools:text="11:45" /> tools:text="11:45" />
<TextView <include
android:id="@+id/text_total_unread_messages" android:id="@+id/layout_unread_messages_badge"
style="@style/TextAppearance.AppCompat.Caption" layout="@layout/unread_messages_badge"
android:layout_width="20dp" android:layout_width="20dp"
android:layout_height="20dp" android:layout_height="20dp"
android:background="@drawable/style_total_unread_messages"
android:gravity="center"
android:textColor="@color/white"
android:textSize="10sp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/guideline_two" app:layout_constraintBottom_toTopOf="@+id/guideline_two"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent" />
tools:text="99+"
tools:visibility="visible" />
<android.support.constraint.Guideline <android.support.constraint.Guideline
android:id="@+id/guideline_one" android:id="@+id/guideline_one"
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/button_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_arrow_downward_24dp"
android:theme="@style/Theme.AppCompat"
android:tint="@color/gray_material"
android:visibility="invisible"
app:backgroundTint="@color/white"
app:fabSize="mini"
app:layout_anchor="@id/recycler_view"
app:layout_anchorGravity="bottom|end" />
</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_total_unread_messages"
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/style_total_unread_messages"
android:gravity="center"
android:textColor="@color/white"
android:textSize="10sp"
android:visibility="gone"
tools:text="99+"
tools:visibility="visible" />
</LinearLayout>
\ No newline at end of file
...@@ -80,4 +80,7 @@ ...@@ -80,4 +80,7 @@
<!-- Pinned Messages --> <!-- Pinned Messages -->
<string name="title_pinned_messages">Mensagens Pinadas</string> <string name="title_pinned_messages">Mensagens Pinadas</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">Tamanho de arquivo (%1$d bytes) excedeu tamanho máximo de upload (%2$d bytes)</string>
</resources> </resources>
\ No newline at end of file
...@@ -82,4 +82,7 @@ ...@@ -82,4 +82,7 @@
<!-- Pinned Messages --> <!-- Pinned Messages -->
<string name="title_pinned_messages">Pinned Messages</string> <string name="title_pinned_messages">Pinned Messages</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">File size %1$d bytes exceeded max upload size of %2$d bytes</string>
</resources> </resources>
\ No newline at end of file
...@@ -13,7 +13,7 @@ buildscript { ...@@ -13,7 +13,7 @@ buildscript {
classpath "com.android.tools.build:gradle:3.0.1" classpath "com.android.tools.build:gradle:3.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.1.2' classpath 'com.google.gms:google-services:3.2.0'
classpath 'io.fabric.tools:gradle:1.+' classpath 'io.fabric.tools:gradle:1.+'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
...@@ -27,7 +27,6 @@ allprojects { ...@@ -27,7 +27,6 @@ allprojects {
jcenter() jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url "http://dl.bintray.com/amulyakhare/maven" } // For TextDrawable.
} }
apply from: rootProject.file('dependencies.gradle') apply from: rootProject.file('dependencies.gradle')
......
...@@ -16,21 +16,19 @@ ext { ...@@ -16,21 +16,19 @@ ext {
playServices : '11.8.0', playServices : '11.8.0',
room : '1.0.0', room : '1.0.0',
rxKotlin : '2.2.0', rxKotlin : '2.2.0',
rxAndroid : '2.0.1', rxAndroid : '2.0.2',
moshi : '1.6.0-SNAPSHOT', moshi : '1.6.0-SNAPSHOT',
okhttp : '3.9.0', okhttp : '3.9.1',
timber : '4.5.1', timber : '4.6.1',
threeTenABP : '1.0.5', threeTenABP : '1.0.5',
rxBinding : '2.0.0', rxBinding : '2.0.0',
fresco : '1.7.1', fresco : '1.8.1',
kotshi : '0.3.0', kotshi : '0.3.0',
frescoImageViewer : '0.5.0', frescoImageViewer : '0.5.0',
androidSvg : '1.2.1', androidSvg : 'master-SNAPSHOT',
aVLoadingIndicatorView : '2.1.3',
markwon : '1.0.3', markwon : '1.0.3',
textDrawable : '1.0.2', // Remove this library after https://github.com/RocketChat/Rocket.Chat/pull/9492 is merged
moshiLazyAdapters : '2.1', // Even declared on the SDK we need to add this library here because java.lang.NoClassDefFoundError: Failed resolution of: Lcom/serjltt/moshi/adapters/FallbackEnum;
sheetMenu : '1.3.3', sheetMenu : '1.3.3',
aVLoadingIndicatorView : '2.1.3',
// For testing // For testing
junit : '4.12', junit : '4.12',
...@@ -84,20 +82,15 @@ ext { ...@@ -84,20 +82,15 @@ ext {
kotshiCompiler : "se.ansman.kotshi:compiler:${versions.kotshi}", kotshiCompiler : "se.ansman.kotshi:compiler:${versions.kotshi}",
frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}", frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}",
androidSvg : "com.github.BigBadaboom:androidsvg:${versions.androidSvg}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
textDrawable : "com.github.rocketchat:textdrawable:${versions.textDrawable}",
markwon : "ru.noties:markwon:${versions.markwon}", markwon : "ru.noties:markwon:${versions.markwon}",
markwonImageLoader : "ru.noties:markwon-image-loader:${versions.markwon}", markwonImageLoader : "ru.noties:markwon-image-loader:${versions.markwon}",
moshiLazyAdapters : "com.serjltt.moshi:moshi-lazy-adapters:${versions.moshiLazyAdapters}",
sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}", sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}",
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
// For testing // For testing
junit : "junit:junit:$versions.junit", junit : "junit:junit:$versions.junit",
expressoCore : "com.android.support.test.espresso:espresso-core:${versions.expresso}", expressoCore : "com.android.support.test.espresso:espresso-core:${versions.expresso}",
......
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