Commit befa16fc authored by Lucio Maciel's avatar Lucio Maciel

Merge branch 'filipedelimabrito-feature/document-upload' into develop-2.x

parents 7455ab37 de94ddbf
...@@ -36,11 +36,11 @@ ...@@ -36,11 +36,11 @@
</activity> </activity>
<activity <activity
android:name=".webview.WebViewActivity" android:name=".main.ui.MainActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity <activity
android:name=".main.ui.MainActivity" android:name=".webview.WebViewActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity <activity
...@@ -82,7 +82,6 @@ ...@@ -82,7 +82,6 @@
<meta-data <meta-data
android:name="io.fabric.ApiKey" android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" /> android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -7,7 +7,7 @@ import chat.rocket.android.helper.NetworkHelper ...@@ -7,7 +7,7 @@ import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -95,7 +95,10 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -95,7 +95,10 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
try { try {
val token = client.login(usernameOrEmail, password) val token = client.login(usernameOrEmail, password)
val me = client.me() val me = client.me()
multiServerRepository.save(server, TokenModel(token.userId, token.authToken)) multiServerRepository.save(
server,
TokenModel(token.userId, token.authToken)
)
localRepository.save(LocalRepository.USERNAME_KEY, me.username) localRepository.save(LocalRepository.USERNAME_KEY, me.username)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
......
...@@ -12,17 +12,16 @@ import android.view.ViewGroup ...@@ -12,17 +12,16 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.login.presentation.LoginPresenter import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.AnimationHelper import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.textContent import chat.rocket.android.util.extensions.textContent
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.* import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import javax.inject.Inject import javax.inject.Inject
...@@ -237,4 +236,4 @@ class LoginFragment : Fragment(), LoginView { ...@@ -237,4 +236,4 @@ class LoginFragment : Fragment(), LoginView {
scroll_view.fullScroll(ScrollView.FOCUS_DOWN) scroll_view.fullScroll(ScrollView.FOCUS_DOWN)
}, 1250) }, 1250)
} }
} }
\ No newline at end of file
...@@ -8,7 +8,7 @@ import chat.rocket.android.authentication.signup.ui.SignupFragment ...@@ -8,7 +8,7 @@ import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.util.addFragmentBackStack import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.webview.webViewIntent import chat.rocket.android.webview.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity, internal val context: Context) { class AuthenticationNavigator(internal val activity: AuthenticationActivity, internal val context: Context) {
......
...@@ -6,7 +6,7 @@ import chat.rocket.android.helper.NetworkHelper ...@@ -6,7 +6,7 @@ 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.*
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.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.RocketChatClient
import chat.rocket.core.internal.rest.settings import chat.rocket.core.internal.rest.settings
...@@ -56,8 +56,8 @@ class ServerPresenter @Inject constructor(private val view: ServerView, ...@@ -56,8 +56,8 @@ class ServerPresenter @Inject constructor(private val view: ServerView,
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} finally { } finally {
view.hideLoading() view.hideLoading()
} }
......
...@@ -10,7 +10,7 @@ import chat.rocket.android.R ...@@ -10,7 +10,7 @@ import chat.rocket.android.R
import chat.rocket.android.authentication.server.presentation.ServerPresenter import chat.rocket.android.authentication.server.presentation.ServerPresenter
import chat.rocket.android.authentication.server.presentation.ServerView import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.util.* import chat.rocket.android.util.extensions.*
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_server.* import kotlinx.android.synthetic.main.fragment_authentication_server.*
import javax.inject.Inject import javax.inject.Inject
......
...@@ -7,7 +7,7 @@ import chat.rocket.android.helper.UrlHelper ...@@ -7,7 +7,7 @@ import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
...@@ -50,8 +50,10 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -50,8 +50,10 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
view.showLoading() view.showLoading()
try { try {
client.signup(email, name, username, password) // TODO This function returns a user so should we save it? // TODO This function returns a user so should we save it?
client.login(username, password) // TODO This function returns a user token so should we save it? client.signup(email, name, username, password)
// TODO This function returns a user token so should we save it?
client.login(username, password)
val me = client.me() val me = client.me()
localRepository.save(LocalRepository.USERNAME_KEY, me.username) localRepository.save(LocalRepository.USERNAME_KEY, me.username)
registerPushToken() registerPushToken()
......
...@@ -14,9 +14,9 @@ import chat.rocket.android.authentication.signup.presentation.SignupView ...@@ -14,9 +14,9 @@ import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.AnimationHelper import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.showToast import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.textContent import chat.rocket.android.util.extensions.showToast
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.* import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import javax.inject.Inject import javax.inject.Inject
......
...@@ -8,7 +8,7 @@ import chat.rocket.android.infrastructure.LocalRepository ...@@ -8,7 +8,7 @@ import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.MultiServerTokenRepository import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -43,8 +43,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -43,8 +43,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
view.showLoading() view.showLoading()
try { try {
// The token is saved via the client TokenProvider // The token is saved via the client TokenProvider
val token = client.login(usernameOrEmail, password, twoFactorAuthenticationCode) val token =
multiServerRepository.save(server, TokenModel(token.userId, token.authToken)) client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
multiServerRepository.save(
server,
TokenModel(token.userId, token.authToken)
)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
......
...@@ -9,14 +9,13 @@ import android.view.LayoutInflater ...@@ -9,14 +9,13 @@ 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 chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.helper.AnimationHelper import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.showToast import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.textContent import chat.rocket.android.util.extensions.showToast
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.* import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import javax.inject.Inject import javax.inject.Inject
...@@ -111,4 +110,4 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -111,4 +110,4 @@ class TwoFAFragment : Fragment(), TwoFAView {
presenter.authenticate(username, password, text_two_factor_auth.textContent) presenter.authenticate(username, password, text_two_factor_auth.textContent)
} }
} }
} }
\ No newline at end of file
...@@ -6,7 +6,7 @@ import android.support.v7.app.AppCompatActivity ...@@ -6,7 +6,7 @@ import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.presentation.AuthenticationPresenter import chat.rocket.android.authentication.presentation.AuthenticationPresenter
import chat.rocket.android.authentication.server.ui.ServerFragment import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.util.addFragment import chat.rocket.android.util.extensions.addFragment
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
......
package chat.rocket.android.chatroom.domain
import android.content.Context
import android.net.Uri
import chat.rocket.android.util.extensions.getFileName
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
class UriInteractor @Inject constructor(private val context: Context) {
/**
* Gets the file name from an [Uri].
*/
fun getFileName(uri: Uri): String? = uri.getFileName(context)
/**
* Gets the MimeType of an [Uri]
*/
fun getMimeType(uri: Uri): String = uri.getMimeType(context)
/**
* Gets the real path of an [Uri]
*/
fun getRealPath(uri: Uri): String? = uri.getRealPathFromURI(context)
/**
* Save the contents of an [Uri] to a temp file named after uri.getFileName()
*/
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
package chat.rocket.android.chatroom.presentation package chat.rocket.android.chatroom.presentation
import android.net.Uri
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.domain.UriInteractor
import chat.rocket.android.chatroom.viewmodel.MessageViewModelMapper import chat.rocket.android.chatroom.viewmodel.MessageViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
...@@ -18,9 +20,11 @@ import chat.rocket.core.internal.rest.* ...@@ -18,9 +20,11 @@ import chat.rocket.core.internal.rest.*
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Value import chat.rocket.core.model.Value
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
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,
...@@ -28,6 +32,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -28,6 +32,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
getSettingsInteractor: GetSettingsInteractor, getSettingsInteractor: GetSettingsInteractor,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val permissions: GetPermissionsInteractor, private val permissions: GetPermissionsInteractor,
private val uriInteractor: UriInteractor,
private val messagesRepository: MessagesRepository, private val messagesRepository: MessagesRepository,
factory: RocketChatClientFactory, factory: RocketChatClientFactory,
private val mapper: MessageViewModelMapper) { private val mapper: MessageViewModelMapper) {
...@@ -41,7 +46,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -41,7 +46,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
val messages = client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result val messages =
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
messagesRepository.saveAll(messages) messagesRepository.saveAll(messages)
val messagesViewModels = mapper.mapToViewModelList(messages, settings) val messagesViewModels = mapper.mapToViewModelList(messages, settings)
...@@ -68,11 +74,10 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -68,11 +74,10 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
launchUI(strategy) { launchUI(strategy) {
view.disableMessageInput() view.disableMessageInput()
try { try {
val message: Message val message = if (messageId == null) {
if (messageId == null) { client.sendMessage(chatRoomId, text)
message = client.sendMessage(chatRoomId, text)
} else { } else {
message = client.updateMessage(chatRoomId, messageId, text) client.updateMessage(chatRoomId, messageId, text)
} }
// ignore message for now, will receive it on the stream // ignore message for now, will receive it on the stream
view.enableMessageInput(clear = true) view.enableMessageInput(clear = true)
...@@ -83,12 +88,52 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -83,12 +88,52 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
view.enableMessageInput() view.enableMessageInput()
} }
} }
} }
fun uploadFile(roomId: String, uri: Uri, msg: String) {
launchUI(strategy) {
view.showLoading()
var tempFile: File? = null
try {
val fileName = async { uriInteractor.getFileName(uri) }.await()
val mimeType = async { uriInteractor.getMimeType(uri) }.await()
/* FIXME - this is a workaround for uploading files with the SDK
*
* 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) {
view.showInvalidFileMessage()
} else {
client.uploadFile(roomId, tempFile, mimeType, msg, fileName)
}
} catch (ex: RocketChatException) {
Timber.d(ex)
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
tempFile?.delete()
view.hideLoading()
}
}
}
fun subscribeMessages(roomId: String) { fun subscribeMessages(roomId: String) {
client.addStateChannel(stateChannel) client.addStateChannel(stateChannel)
launch(CommonPool + strategy.jobs) { launch(CommonPool + strategy.jobs) {
...@@ -182,7 +227,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -182,7 +227,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
message?.let { m -> message?.let { m ->
val id = m.id val id = m.id
val username = m.sender?.username val username = m.sender?.username
val user = "@" + if (settings.useRealName()) m.sender?.name ?: m.sender?.username else m.sender?.username val user = "@" + if (settings.useRealName()) m.sender?.name
?: m.sender?.username else m.sender?.username
val mention = if (mentionAuthor && me.username != username) user else "" val mention = if (mentionAuthor && me.username != username) user else ""
val type = roomTypeOf(roomType) val type = roomTypeOf(roomType)
val room = when (type) { val room = when (type) {
...@@ -192,7 +238,11 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -192,7 +238,11 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
is RoomType.Livechat -> "livechat" is RoomType.Livechat -> "livechat"
is RoomType.Custom -> "custom" //TODO: put appropriate callback string here. is RoomType.Custom -> "custom" //TODO: put appropriate callback string here.
} }
view.showReplyingAction(user, "[ ](${serverUrl}/${room}/${roomName}?msg=${id}) ${mention} ", m.message) view.showReplyingAction(
user,
"[ ](${serverUrl}/${room}/${roomName}?msg=${id}) ${mention} ",
m.message
)
} }
} }
} }
...@@ -265,7 +315,6 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -265,7 +315,6 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
if (message.roomId != roomId) { if (message.roomId != roomId) {
Timber.d("Ignoring message for room ${message.roomId}, expecting $roomId") Timber.d("Ignoring message for room ${message.roomId}, expecting $roomId")
} }
updateMessage(message) updateMessage(message)
} }
} }
......
package chat.rocket.android.chatroom.presentation package chat.rocket.android.chatroom.presentation
import android.net.Uri
import chat.rocket.android.chatroom.viewmodel.MessageViewModel import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
...@@ -20,6 +21,18 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -20,6 +21,18 @@ interface ChatRoomView : LoadingView, MessageView {
*/ */
fun sendMessage(text: String) fun sendMessage(text: String)
/**
* Uploads a file to a chat room.
*
* @param uri The file URI to send.
*/
fun uploadFile(uri: Uri)
/**
* Shows a invalid file message.
*/
fun showInvalidFileMessage()
/** /**
* Shows a (recent) message sent to a chat room. * Shows a (recent) message sent to a chat room.
* *
...@@ -63,5 +76,6 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -63,5 +76,6 @@ interface ChatRoomView : LoadingView, MessageView {
fun showEditingAction(roomId: String, messageId: String, text: String) fun showEditingAction(roomId: String, messageId: String, text: String)
fun disableMessageInput() fun disableMessageInput()
fun enableMessageInput(clear: Boolean = false) fun enableMessageInput(clear: Boolean = false)
} }
\ No newline at end of file
...@@ -6,7 +6,7 @@ import chat.rocket.android.server.domain.GetChatRoomsInteractor ...@@ -6,7 +6,7 @@ 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.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.getRoomPinnedMessages import chat.rocket.core.internal.rest.getRoomPinnedMessages
...@@ -38,9 +38,11 @@ class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessag ...@@ -38,9 +38,11 @@ class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessag
val chatRoom = roomsInteractor.getById(serverUrl, roomId) val chatRoom = roomsInteractor.getById(serverUrl, roomId)
chatRoom?.let { room -> chatRoom?.let { room ->
view.showLoading() view.showLoading()
val pinnedMessages = client.getRoomPinnedMessages(roomId, room.type, pinnedMessagesListOffset) val pinnedMessages =
client.getRoomPinnedMessages(roomId, room.type, pinnedMessagesListOffset)
pinnedMessagesListOffset = pinnedMessages.offset.toInt() pinnedMessagesListOffset = pinnedMessages.offset.toInt()
val messageList = mapper.mapToViewModelList(pinnedMessages.result, settings).filterNot { it.isSystemMessage } val messageList = mapper.mapToViewModelList(pinnedMessages.result, settings)
.filterNot { it.isSystemMessage }
view.showPinnedMessages(messageList) view.showPinnedMessages(messageList)
view.hideLoading() view.hideLoading()
}.ifNull { }.ifNull {
......
...@@ -4,7 +4,6 @@ import android.graphics.drawable.Drawable ...@@ -4,7 +4,6 @@ import android.graphics.drawable.Drawable
import android.support.design.widget.BaseTransientBottomBar import android.support.design.widget.BaseTransientBottomBar
import android.support.v4.view.ViewCompat import android.support.v4.view.ViewCompat
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -12,9 +11,8 @@ import android.widget.ImageView ...@@ -12,9 +11,8 @@ 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.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.util.content import chat.rocket.android.util.extensions.content
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import javax.inject.Inject
class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> { class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> {
...@@ -66,19 +64,21 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> { ...@@ -66,19 +64,21 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> {
super(parentViewGroup, content, contentViewCallback) super(parentViewGroup, content, contentViewCallback)
class CallbackImpl(val content: View) : BaseTransientBottomBar.ContentViewCallback { class CallbackImpl(val content: View) : BaseTransientBottomBar.ContentViewCallback {
override fun animateContentOut(delay: Int, duration: Int) { override fun animateContentOut(delay: Int, duration: Int) {
ViewCompat.setScaleY(content, 1f) ViewCompat.setScaleY(content, 1f)
ViewCompat.animate(content) ViewCompat.animate(content)
.scaleY(0f) .scaleY(0f)
.setDuration(duration.toLong()) .setDuration(duration.toLong())
.setStartDelay(delay.toLong()) .startDelay = delay.toLong()
} }
override fun animateContentIn(delay: Int, duration: Int) { override fun animateContentIn(delay: Int, duration: Int) {
ViewCompat.setScaleY(content, 0f) ViewCompat.setScaleY(content, 0f)
ViewCompat.animate(content) ViewCompat.animate(content)
.scaleY(1f).setDuration(duration.toLong()) .scaleY(1f)
.setStartDelay(delay.toLong()) .setDuration(duration.toLong())
.startDelay = delay.toLong()
} }
} }
} }
\ No newline at end of file
...@@ -6,8 +6,8 @@ import android.os.Bundle ...@@ -6,8 +6,8 @@ import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.util.addFragment import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.util.textContent import chat.rocket.android.util.extensions.textContent
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
......
...@@ -5,7 +5,6 @@ import android.text.method.LinkMovementMethod ...@@ -5,7 +5,6 @@ import android.text.method.LinkMovementMethod
import android.view.MenuItem import android.view.MenuItem
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.chatroom.presentation.ChatRoomPresenter import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
...@@ -14,10 +13,9 @@ import chat.rocket.android.chatroom.ui.bottomsheet.adapter.ActionListAdapter ...@@ -14,10 +13,9 @@ import chat.rocket.android.chatroom.ui.bottomsheet.adapter.ActionListAdapter
import chat.rocket.android.chatroom.viewmodel.AttachmentType import chat.rocket.android.chatroom.viewmodel.AttachmentType
import chat.rocket.android.chatroom.viewmodel.MessageViewModel import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.player.PlayerActivity import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.content import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.common.util.ifNull
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.avatar.view.*
...@@ -29,26 +27,23 @@ import ru.whalemare.sheetmenu.extension.toList ...@@ -29,26 +27,23 @@ import ru.whalemare.sheetmenu.extension.toList
class ChatRoomAdapter(private val roomType: String, class ChatRoomAdapter(private val roomType: String,
private val roomName: String, private val roomName: String,
private val presenter: ChatRoomPresenter) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() { private val presenter: ChatRoomPresenter) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() {
private val dataSet = ArrayList<MessageViewModel>()
init { init {
setHasStableIds(true) setHasStableIds(true)
} }
val dataSet = ArrayList<MessageViewModel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(parent.inflate(R.layout.item_message), roomType, roomName, presenter) ViewHolder(parent.inflate(R.layout.item_message), roomType, roomName, presenter)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position]) override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position])
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
onBindViewHolder(holder, position)
}
override fun getItemCount(): Int = dataSet.size override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position override fun getItemViewType(position: Int): Int = position
override fun getItemId(position: Int): Long = dataSet[position].id.hashCode().toLong()
fun addDataSet(dataSet: List<MessageViewModel>) { fun addDataSet(dataSet: List<MessageViewModel>) {
val previousDataSetSize = this.dataSet.size val previousDataSetSize = this.dataSet.size
this.dataSet.addAll(previousDataSetSize, dataSet) this.dataSet.addAll(previousDataSetSize, dataSet)
...@@ -76,27 +71,21 @@ class ChatRoomAdapter(private val roomType: String, ...@@ -76,27 +71,21 @@ class ChatRoomAdapter(private val roomType: String,
} }
} }
override fun getItemId(position: Int): Long {
return dataSet[position].id.hashCode().toLong()
}
class ViewHolder(itemView: View, class ViewHolder(itemView: View,
val roomType: String, val roomType: String,
val roomName: String, val roomName: String,
val presenter: ChatRoomPresenter) : RecyclerView.ViewHolder(itemView), MenuItem.OnMenuItemClickListener { val presenter: ChatRoomPresenter) : RecyclerView.ViewHolder(itemView), MenuItem.OnMenuItemClickListener {
private lateinit var messageViewModel: MessageViewModel private lateinit var messageViewModel: MessageViewModel
fun bind(message: MessageViewModel) = with(itemView) { fun bind(message: MessageViewModel) = with(itemView) {
messageViewModel = message messageViewModel = message
bindUserAvatar(message, image_avatar, image_unknown_avatar)
text_user_name.content = message.sender image_avatar.setImageURI(message.avatarUri)
text_sender.text = message.senderName
text_message_time.content = message.time text_message_time.content = message.time
text_content.content = message.content text_content.content = message.content
text_content.movementMethod = LinkMovementMethod() text_content.movementMethod = LinkMovementMethod()
bindAttachment(message, message_attachment, image_attachment, audio_video_attachment, file_name)
bindAttachment(message, message_attachment, image_attachment, audio_video_attachment,
file_name)
text_content.setOnClickListener { text_content.setOnClickListener {
if (!message.isSystemMessage) { if (!message.isSystemMessage) {
...@@ -134,7 +123,6 @@ class ChatRoomAdapter(private val roomType: String, ...@@ -134,7 +123,6 @@ class ChatRoomAdapter(private val roomType: String,
else -> TODO("Not implemented") else -> TODO("Not implemented")
} }
} }
return true return true
} }
...@@ -180,14 +168,5 @@ class ChatRoomAdapter(private val roomType: String, ...@@ -180,14 +168,5 @@ class ChatRoomAdapter(private val roomType: String,
file_name.text = message.attachmentTitle file_name.text = message.attachmentTitle
} }
} }
private fun bindUserAvatar(message: MessageViewModel, drawee: SimpleDraweeView, imageUnknownAvatar: ImageView) = message.getAvatarUrl().let {
drawee.setImageURI(it.toString())
drawee.setVisible(true)
imageUnknownAvatar.setVisible(false)
}.ifNull {
drawee.setVisible(false)
imageUnknownAvatar.setVisible(true)
}
} }
} }
\ No newline at end of file
package chat.rocket.android.chatroom.ui package chat.rocket.android.chatroom.ui
import android.app.Activity
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
...@@ -16,16 +19,13 @@ import chat.rocket.android.chatroom.presentation.ChatRoomView ...@@ -16,16 +19,13 @@ import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.viewmodel.MessageViewModel import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.util.inflate import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.setVisible
import chat.rocket.android.util.showToast
import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
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_composer.* import kotlinx.android.synthetic.main.message_composer.*
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 {
return ChatRoomFragment().apply { return ChatRoomFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
...@@ -41,19 +41,29 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id" ...@@ -41,19 +41,29 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
private const val BUNDLE_CHAT_ROOM_NAME = "chat_room_name" private const val BUNDLE_CHAT_ROOM_NAME = "chat_room_name"
private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type" private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only" private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
private const val REQUEST_CODE_FOR_PERFORM_SAF = 42
class ChatRoomFragment : Fragment(), ChatRoomView { class ChatRoomFragment : Fragment(), ChatRoomView {
@Inject lateinit var presenter: ChatRoomPresenter @Inject lateinit var presenter: ChatRoomPresenter
@Inject lateinit var parser: MessageParser @Inject lateinit var parser: MessageParser
private lateinit var adapter: ChatRoomAdapter
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
private lateinit var chatRoomName: String private lateinit var chatRoomName: String
private lateinit var chatRoomType: String private lateinit var chatRoomType: String
private var isChatRoomReadOnly: Boolean = false private var isChatRoomReadOnly: Boolean = false
private lateinit var adapter: ChatRoomAdapter
private lateinit var actionSnackbar: ActionSnackbar private lateinit var actionSnackbar: ActionSnackbar
private var citation: String? = null private var citation: String? = null
private var editingMessageId: String? = null private var editingMessageId: String? = null
// For reveal and unreveal anim.
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 centerX by lazy { recycler_view.right }
private val centerY by lazy { recycler_view.bottom }
val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
...@@ -81,9 +91,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -81,9 +91,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
override fun onDestroyView() { override fun onDestroyView() {
presenter.unsubscribeMessages() presenter.unsubscribeMessages()
handler.removeCallbacksAndMessages(null)
super.onDestroyView() super.onDestroyView()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
uploadFile(resultData.data)
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.chatroom_actions, menu) inflater.inflate(R.menu.chatroom_actions, menu)
...@@ -119,7 +138,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -119,7 +138,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
}) })
} }
} }
adapter.addDataSet(dataSet) adapter.addDataSet(dataSet)
} }
} }
...@@ -130,6 +148,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -130,6 +148,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
} }
} }
override fun uploadFile(uri: Uri) {
// TODO Just leaving a blank message that comes with the file for now. In the future lets add the possibility to add a message with the file to be uploaded.
presenter.uploadFile(chatRoomId, uri, "")
}
override fun showInvalidFileMessage() = showMessage(getString(R.string.msg_invalid_file))
override fun showNewMessage(message: MessageViewModel) { override fun showNewMessage(message: MessageViewModel) {
text_message.textContent = "" text_message.textContent = ""
adapter.addItem(message) adapter.addItem(message)
...@@ -137,12 +162,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -137,12 +162,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
} }
override fun disableMessageInput() { override fun disableMessageInput() {
text_send.isEnabled = false button_send.isEnabled = false
text_message.isEnabled = false text_message.isEnabled = false
} }
override fun enableMessageInput(clear: Boolean) { override fun enableMessageInput(clear: Boolean) {
text_send.isEnabled = true button_send.isEnabled = true
text_message.isEnabled = true text_message.isEnabled = true
if (clear) text_message.textContent = "" if (clear) text_message.textContent = ""
} }
...@@ -177,7 +202,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -177,7 +202,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
override fun copyToClipboard(message: String) { override fun copyToClipboard(message: String) {
activity?.apply { activity?.apply {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText("", message)) clipboard.primaryClip = ClipData.newPlainText("", message)
} }
} }
...@@ -195,14 +220,51 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -195,14 +220,51 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
private fun setupComposer() { private fun setupComposer() {
if (isChatRoomReadOnly) { if (isChatRoomReadOnly) {
text_room_is_read_only.setVisible(true) text_room_is_read_only.setVisible(true)
top_container.setVisible(false) input_container.setVisible(false)
} else { } else {
text_send.setOnClickListener { var playAnimation = true
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
}
})
button_send.setOnClickListener {
var textMessage = citation ?: "" var textMessage = citation ?: ""
textMessage = textMessage + text_message.textContent textMessage += text_message.textContent
sendMessage(textMessage) sendMessage(textMessage)
clearActionMessage() clearActionMessage()
} }
button_show_attachment_options.setOnClickListener {
if (layout_message_attachment_options.isShown) {
hideAttachmentOptions()
} else {
showAttachmentOptions()
}
}
view_dim.setOnClickListener { hideAttachmentOptions() }
button_files.setOnClickListener {
handler.postDelayed({
performSAF()
}, 300)
handler.postDelayed({
hideAttachmentOptions()
}, 600)
}
} }
} }
...@@ -219,4 +281,26 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -219,4 +281,26 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
text_message.text.clear() text_message.text.clear()
actionSnackbar.dismiss() actionSnackbar.dismiss()
} }
private fun showAttachmentOptions() {
view_dim.setVisible(true)
// Play anim.
button_show_attachment_options.rotateBy(45F)
layout_message_attachment_options.circularRevealOrUnreveal(centerX, centerY, 0F, hypotenuse)
}
private fun hideAttachmentOptions() {
// Play anim.
button_show_attachment_options.rotateBy(-45F)
layout_message_attachment_options.circularRevealOrUnreveal(centerX, centerY, max, 0F)
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,8 +4,8 @@ import android.os.Bundle ...@@ -4,8 +4,8 @@ import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.util.addFragment import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.util.textContent import chat.rocket.android.util.extensions.textContent
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
......
...@@ -10,10 +10,9 @@ import chat.rocket.android.R ...@@ -10,10 +10,9 @@ import chat.rocket.android.R
import chat.rocket.android.chatroom.viewmodel.AttachmentType import chat.rocket.android.chatroom.viewmodel.AttachmentType
import chat.rocket.android.chatroom.viewmodel.MessageViewModel import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.player.PlayerActivity import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.content import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.common.util.ifNull
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.avatar.view.*
...@@ -78,8 +77,8 @@ class PinnedMessagesAdapter : RecyclerView.Adapter<PinnedMessagesAdapter.ViewHol ...@@ -78,8 +77,8 @@ class PinnedMessagesAdapter : RecyclerView.Adapter<PinnedMessagesAdapter.ViewHol
fun bind(message: MessageViewModel) = with(itemView) { fun bind(message: MessageViewModel) = with(itemView) {
messageViewModel = message messageViewModel = message
bindUserAvatar(message, image_avatar, image_unknown_avatar) image_avatar.setImageURI(message.avatarUri)
text_user_name.content = message.sender text_sender.content = message.senderName
text_message_time.content = message.time text_message_time.content = message.time
text_content.content = message.content text_content.content = message.content
text_content.movementMethod = LinkMovementMethod() text_content.movementMethod = LinkMovementMethod()
...@@ -130,16 +129,5 @@ class PinnedMessagesAdapter : RecyclerView.Adapter<PinnedMessagesAdapter.ViewHol ...@@ -130,16 +129,5 @@ class PinnedMessagesAdapter : RecyclerView.Adapter<PinnedMessagesAdapter.ViewHol
file_name.text = message.attachmentTitle file_name.text = message.attachmentTitle
} }
} }
private fun bindUserAvatar(message: MessageViewModel, drawee: SimpleDraweeView, imageUnknownAvatar: ImageView) {
message.getAvatarUrl().let {
drawee.setImageURI(it.toString())
drawee.setVisible(true)
imageUnknownAvatar.setVisible(false)
}.ifNull {
drawee.setVisible(false)
imageUnknownAvatar.setVisible(true)
}
}
} }
} }
\ No newline at end of file
...@@ -13,8 +13,8 @@ import chat.rocket.android.chatroom.presentation.PinnedMessagesPresenter ...@@ -13,8 +13,8 @@ import chat.rocket.android.chatroom.presentation.PinnedMessagesPresenter
import chat.rocket.android.chatroom.presentation.PinnedMessagesView import chat.rocket.android.chatroom.presentation.PinnedMessagesView
import chat.rocket.android.chatroom.viewmodel.MessageViewModel import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.showToast import chat.rocket.android.util.extensions.showToast
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_pinned_messages.* import kotlinx.android.synthetic.main.fragment_pinned_messages.*
import javax.inject.Inject import javax.inject.Inject
......
...@@ -2,7 +2,7 @@ package chat.rocket.android.chatroom.ui.bottomsheet.adapter ...@@ -2,7 +2,7 @@ package chat.rocket.android.chatroom.ui.bottomsheet.adapter
import android.view.MenuItem import android.view.MenuItem
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
/** /**
* An adapter for bottomsheet menu that lists all the actions that could be taken over a chat message. * An adapter for bottomsheet menu that lists all the actions that could be taken over a chat message.
......
...@@ -13,10 +13,7 @@ import chat.rocket.android.R ...@@ -13,10 +13,7 @@ import chat.rocket.android.R
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.useRealName
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType.* import chat.rocket.core.model.MessageType.*
...@@ -34,9 +31,10 @@ data class MessageViewModel(val context: Context, ...@@ -34,9 +31,10 @@ data class MessageViewModel(val context: Context,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val currentServerRepository: CurrentServerRepository) { private val currentServerRepository: CurrentServerRepository) {
val id: String = message.id val id: String = message.id
val avatarUri: String?
val roomId: String = message.roomId val roomId: String = message.roomId
val time: CharSequence val time: CharSequence
val sender: CharSequence val senderName: CharSequence
val content: CharSequence val content: CharSequence
var quote: Message? = null var quote: Message? = null
var urlsWithMeta = arrayListOf<Url>() var urlsWithMeta = arrayListOf<Url>()
...@@ -50,11 +48,13 @@ data class MessageViewModel(val context: Context, ...@@ -50,11 +48,13 @@ data class MessageViewModel(val context: Context,
var isSystemMessage: Boolean = false var isSystemMessage: Boolean = false
var isPinned: Boolean = false var isPinned: Boolean = false
var currentUsername: String? = null var currentUsername: String? = null
private val baseUrl = settings.get(SITE_URL)
init { init {
currentUsername = localRepository.get(LocalRepository.USERNAME_KEY) currentUsername = localRepository.get(LocalRepository.USERNAME_KEY)
sender = getSenderName() avatarUri = getUserAvatar()
time = getTime(message.timestamp) time = getTime(message.timestamp)
senderName = getSender()
isPinned = message.pinned isPinned = message.pinned
val baseUrl = settings.get(SITE_URL) val baseUrl = settings.get(SITE_URL)
...@@ -100,39 +100,40 @@ data class MessageViewModel(val context: Context, ...@@ -100,39 +100,40 @@ data class MessageViewModel(val context: Context,
} }
} }
} }
content = getContent(context) content = getContent(context)
} }
private fun makeQuote(quoteUrl: HttpUrl, serverUrl: HttpUrl) { private fun getUserAvatar(): String? {
if (quoteUrl.host() == serverUrl.host()) { val username = message.sender?.username ?: "?"
val msgIdToQuote = quoteUrl.queryParameter("msg") return baseUrl?.let {
if (msgIdToQuote != null) { UrlHelper.getAvatarUrl(baseUrl.value.toString(), username)
quote = messagesRepository.getById(msgIdToQuote)
}
}
}
fun getAvatarUrl(): String? {
return message.sender?.username.let {
return@let UrlHelper.getAvatarUrl(currentServerRepository.get()!!, it.toString())
} }
} }
/**
* Get the original message as a String.
*/
fun getOriginalMessage() = message.message
private fun getTime(timestamp: Long) = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(timestamp)) private fun getTime(timestamp: Long) = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(timestamp))
private fun getSenderName(): CharSequence { private fun getSender(): CharSequence {
val useRealName = settings?.get(USE_REALNAME)?.value as Boolean
val username = message.sender?.username val username = message.sender?.username
val realName = message.sender?.name val realName = message.sender?.name
val senderName = if (settings.useRealName()) realName else username val senderName = if (useRealName) realName else username
return senderName ?: username.toString() return senderName ?: context.getString(R.string.msg_unknown)
} }
private fun makeQuote(quoteUrl: HttpUrl, serverUrl: HttpUrl) {
if (quoteUrl.host() == serverUrl.host()) {
val msgIdToQuote = quoteUrl.queryParameter("msg")
if (msgIdToQuote != null) {
quote = messagesRepository.getById(msgIdToQuote)
}
}
}
/**
* Get the original message as a String.
*/
fun getOriginalMessage() = message.message
private fun getContent(context: Context): CharSequence { private fun getContent(context: Context): CharSequence {
val contentMessage: CharSequence val contentMessage: CharSequence
when (message.type) { when (message.type) {
...@@ -231,4 +232,4 @@ sealed class AttachmentType { ...@@ -231,4 +232,4 @@ sealed class AttachmentType {
object Video : AttachmentType() object Video : AttachmentType()
object Audio : AttachmentType() object Audio : AttachmentType()
object Message : AttachmentType() object Message : AttachmentType()
} }
\ No newline at end of file
...@@ -6,7 +6,7 @@ import chat.rocket.android.server.domain.GetChatRoomsInteractor ...@@ -6,7 +6,7 @@ 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.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.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.model.Subscription import chat.rocket.core.internal.model.Subscription
......
...@@ -10,9 +10,9 @@ import android.widget.ImageView ...@@ -10,9 +10,9 @@ 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.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.common.model.RoomType 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
......
...@@ -9,14 +9,13 @@ import android.support.v7.widget.DefaultItemAnimator ...@@ -9,14 +9,13 @@ import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.util.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.widget.DividerItemDecoration import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
...@@ -158,4 +157,4 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -158,4 +157,4 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
return newRooms[newItemPosition] return newRooms[newItemPosition]
} }
} }
} }
\ No newline at end of file
...@@ -23,7 +23,6 @@ import ru.noties.markwon.renderer.SpannableMarkdownVisitor ...@@ -23,7 +23,6 @@ import ru.noties.markwon.renderer.SpannableMarkdownVisitor
import java.util.regex.Pattern import java.util.regex.Pattern
import javax.inject.Inject import javax.inject.Inject
class MessageParser @Inject constructor(val context: Application, private val configuration: SpannableConfiguration) { class MessageParser @Inject constructor(val context: Application, private val configuration: SpannableConfiguration) {
private val parser = Markwon.createParser() private val parser = Markwon.createParser()
...@@ -60,9 +59,9 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -60,9 +59,9 @@ class MessageParser @Inject constructor(val context: Application, private val co
val parentNode = parser.parse(toLenientMarkdown(content)) val parentNode = parser.parse(toLenientMarkdown(content))
parentNode.accept(QuoteMessageBodyVisitor(context, configuration, builder)) parentNode.accept(QuoteMessageBodyVisitor(context, configuration, builder))
quote?.apply { quote?.apply {
var quoteNode = parser.parse("> $sender $time") var quoteNode = parser.parse("> $senderName $time")
parentNode.appendChild(quoteNode) parentNode.appendChild(quoteNode)
quoteNode.accept(QuoteMessageSenderVisitor(context, configuration, builder, sender.length)) quoteNode.accept(QuoteMessageSenderVisitor(context, configuration, builder, senderName.length))
quoteNode = parser.parse("> ${toLenientMarkdown(quote.getOriginalMessage())}") quoteNode = parser.parse("> ${toLenientMarkdown(quote.getOriginalMessage())}")
quoteNode.accept(QuoteMessageBodyVisitor(context, configuration, builder)) quoteNode.accept(QuoteMessageBodyVisitor(context, configuration, builder))
} }
......
...@@ -5,7 +5,7 @@ import android.text.Spanned ...@@ -5,7 +5,7 @@ import android.text.Spanned
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.util.ifEmpty import chat.rocket.android.util.extensions.ifEmpty
object TextHelper { object TextHelper {
......
...@@ -8,7 +8,7 @@ import android.view.MenuItem ...@@ -8,7 +8,7 @@ import android.view.MenuItem
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.profile.ui.ProfileFragment import chat.rocket.android.profile.ui.ProfileFragment
import chat.rocket.android.util.addFragment import chat.rocket.android.util.extensions.addFragment
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
......
...@@ -4,7 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -4,7 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
...@@ -27,13 +27,18 @@ class ProfilePresenter @Inject constructor (private val view: ProfileView, ...@@ -27,13 +27,18 @@ class ProfilePresenter @Inject constructor (private val view: ProfileView,
val myself = client.me() val myself = client.me()
myselfId = myself.id myselfId = myself.id
val avatarUrl = UrlHelper.getAvatarUrl(serverUrl, myself.username!!) val avatarUrl = UrlHelper.getAvatarUrl(serverUrl, myself.username!!)
view.showProfile(avatarUrl, myself.name!!, myself.username!!, myself.emails?.get(0)?.address!!) view.showProfile(
avatarUrl,
myself.name!!,
myself.username!!,
myself.emails?.get(0)?.address!!
)
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} finally { } finally {
view.hideLoading() view.hideLoading()
} }
...@@ -51,8 +56,8 @@ class ProfilePresenter @Inject constructor (private val view: ProfileView, ...@@ -51,8 +56,8 @@ class ProfilePresenter @Inject constructor (private val view: ProfileView,
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} finally { } finally {
view.hideLoading() view.hideLoading()
} }
......
...@@ -11,7 +11,7 @@ import chat.rocket.android.R ...@@ -11,7 +11,7 @@ import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.profile.presentation.ProfilePresenter import chat.rocket.android.profile.presentation.ProfilePresenter
import chat.rocket.android.profile.presentation.ProfileView import chat.rocket.android.profile.presentation.ProfileView
import chat.rocket.android.util.* import chat.rocket.android.util.extensions.*
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.app_bar.*
...@@ -125,7 +125,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -125,7 +125,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
private fun listenToChanges() { private fun listenToChanges() {
Observables.combineLatest(text_name.getObservable(), text_username.getObservable(), text_email.getObservable()).subscribe({ t -> Observables.combineLatest(text_name.asObservable(), text_username.asObservable(), text_email.asObservable()).subscribe({ t ->
if (t.first.toString() != currentName || t.second.toString() != currentUsername || t.third.toString() != currentEmail) { if (t.first.toString() != currentName || t.second.toString() != currentUsername || t.third.toString() != currentEmail) {
startActionMode() startActionMode()
} else { } else {
...@@ -147,4 +147,4 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -147,4 +147,4 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
text_username.isEnabled = value text_username.isEnabled = value
text_email.isEnabled = value text_email.isEnabled = value
} }
} }
\ No newline at end of file
package chat.rocket.android.util.extensions
import android.view.View
import android.view.ViewAnimationUtils
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
fun View.rotateBy(value: Float, duration: Long = 200) {
animate()
.rotationBy(value)
.setDuration(duration)
.start()
}
fun View.fadeInOrOut(startValue: Float, finishValue: Float, duration: Long = 200) {
animate()
.alpha(startValue)
.setDuration(duration)
.setInterpolator(DecelerateInterpolator())
.withEndAction({
animate()
.alpha(finishValue)
.setDuration(duration)
.setInterpolator(AccelerateInterpolator()).start()
}).start()
if (startValue > finishValue) {
setVisible(false)
} else {
setVisible(true)
}
}
fun View.circularRevealOrUnreveal(centerX: Int, centerY: Int, startRadius: Float, endRadius: Float, duration: Long = 600) {
val anim = ViewAnimationUtils.createCircularReveal(this, centerX, centerY, startRadius, endRadius)
anim.duration = duration
if (startRadius < endRadius) {
setVisible(true)
} else {
setVisible(false)
}
anim.start()
}
\ No newline at end of file
package chat.rocket.android.util package chat.rocket.android.util.extensions
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import kotlinx.coroutines.experimental.CoroutineScope import kotlinx.coroutines.experimental.CoroutineScope
......
package chat.rocket.android.util.extensions
import android.widget.EditText
import com.jakewharton.rxbinding2.widget.RxTextView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
fun EditText.asObservable(debounceTimeout: Long = 100): Observable<CharSequence> {
return RxTextView.textChanges(this)
.debounce(debounceTimeout, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
}
\ No newline at end of file
package chat.rocket.android.util package chat.rocket.android.util.extensions
import android.support.annotation.LayoutRes import android.content.ContentResolver
import android.view.LayoutInflater import android.content.Context
import android.view.View import android.net.Uri
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import android.provider.MediaStore
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import com.jakewharton.rxbinding2.widget.RxTextView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
fun String.ifEmpty(value: String): String { fun String.ifEmpty(value: String): String {
if (isEmpty()) { if (isEmpty()) {
...@@ -26,24 +23,18 @@ fun CharSequence.ifEmpty(value: String): CharSequence { ...@@ -26,24 +23,18 @@ fun CharSequence.ifEmpty(value: String): CharSequence {
return this return this
} }
fun View.setVisible(visible: Boolean) {
visibility = if (visible) {
View.VISIBLE
} else {
View.GONE
}
}
fun ViewGroup.inflate(@LayoutRes resource: Int): View {
return LayoutInflater.from(context).inflate(resource, this, false)
}
var TextView.textContent: String var TextView.textContent: String
get() = text.toString() get() = text.toString()
set(value) { set(value) {
text = value text = value
} }
var TextView.hintContent: String
get() = hint.toString()
set(value) {
hint = value
}
var TextView.content: CharSequence var TextView.content: CharSequence
get() = text get() = text
set(value) { set(value) {
...@@ -54,15 +45,48 @@ var TextView.content: CharSequence ...@@ -54,15 +45,48 @@ var TextView.content: CharSequence
Markwon.scheduleTableRows(this) Markwon.scheduleTableRows(this)
} }
var TextView.hintContent: String fun Uri.getFileName(context: Context): String? {
get() = hint.toString() val cursor = context.contentResolver.query(this, null, null, null, null, null)
set(value) {
hint = value 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 EditText.getObservable(): Observable<CharSequence> { fun Uri.getMimeType(context: Context): String {
return RxTextView.textChanges(this) return if (scheme == ContentResolver.SCHEME_CONTENT) {
.debounce(100, TimeUnit.MILLISECONDS) context.contentResolver.getType(this)
.observeOn(AndroidSchedulers.mainThread()) } else {
.subscribeOn(AndroidSchedulers.mainThread()) 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
package chat.rocket.android.util package chat.rocket.android.util.extensions
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.support.annotation.LayoutRes
import android.support.annotation.StringRes import android.support.annotation.StringRes
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
fun View.setVisible(visible: Boolean) {
visibility = if (visible) {
View.VISIBLE
} else {
View.GONE
}
}
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) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance() val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
...@@ -31,7 +45,9 @@ fun Activity.hideKeyboard() { ...@@ -31,7 +45,9 @@ fun Activity.hideKeyboard() {
} }
fun Activity.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) = showToast(getString(resource), duration) fun Activity.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) = showToast(getString(resource), duration)
fun Activity.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, message, duration).show() fun Activity.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, message, duration).show()
fun Fragment.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) = showToast(getString(resource), duration) fun Fragment.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) = showToast(getString(resource), duration)
fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = activity!!.showToast(message, duration) fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = activity!!.showToast(message, duration)
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF2F343D"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="17.0"
android:viewportWidth="18.0">
<path
android:fillColor="#FF1D74F5"
android:fillType="nonZero"
android:pathData="M4.221,11.799L10.444,5.576C10.97,5.05 11.833,5.067 12.383,5.617C12.928,6.163 12.946,7.034 12.423,7.557L6.195,13.785C5.226,14.754 3.613,14.717 2.599,13.704C1.582,12.687 1.546,11.08 2.52,10.106L9.295,3.331C10.713,1.913 13.07,1.966 14.552,3.448C16.035,4.931 16.087,7.287 14.668,8.706L7.898,15.476L8.747,16.324L15.516,9.555C17.383,7.688 17.316,4.588 15.364,2.636C13.414,0.686 10.313,0.616 8.446,2.483L1.671,9.258C0.248,10.681 0.302,13.03 1.788,14.515C3.27,15.997 5.626,16.051 7.044,14.633L13.272,8.405C14.242,7.435 14.209,5.82 13.195,4.805C12.175,3.786 10.569,3.754 9.595,4.728L3.373,10.95L4.221,11.799Z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="20.0"
android:viewportWidth="20.0">
<path
android:fillColor="#FF2F343D"
android:fillType="evenOdd"
android:pathData="M12.4,7.6m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" />
<path
android:fillColor="#FF2F343D"
android:fillType="evenOdd"
android:pathData="M7.6,7.6m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" />
<path
android:pathData="M10,10m-8.4,0a8.4,8.4 0,1 1,16.8 0a8.4,8.4 0,1 1,-16.8 0"
android:strokeColor="#FF2F343D"
android:strokeWidth="1.5" />
<path
android:pathData="M6.606,12.794L6.606,12.794C8.48,14.669 11.52,14.669 13.394,12.794"
android:strokeColor="#FF2F343D"
android:strokeWidth="1.5" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="20.0"
android:viewportWidth="22.0">
<path
android:fillColor="#FF1D74F5"
android:fillType="nonZero"
android:pathData="M0.869,1.32C0.731,0.987 0.79,0.565 1.045,0.276C1.281,0.01 1.634,-0.079 1.967,0.076L20.645,9.134C20.939,9.29 21.136,9.645 21.136,10C21.155,10.377 20.939,10.71 20.645,10.866L1.967,19.924C1.634,20.079 1.281,19.99 1.045,19.724C0.79,19.435 0.712,19.036 0.869,18.68L4.263,10L0.869,1.32ZM18.193,10L3.262,2.741L5.832,9.29L9.953,9.334C10.404,9.312 10.777,9.734 10.777,10.266C10.796,10.777 10.423,11.199 9.953,11.199L5.636,11.199L3.262,17.259L18.193,10Z" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/colorLightTheme" />
<corners
android:radius="6dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/colorBackgroundContentFileAttachment" />
<corners
android:radius="6dp" />
<stroke
android:width="2dp"
android:color="@color/colorDrawableTintGrey" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/colorBackgroundFileAttachment" />
<corners
android:radius="6dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
...@@ -12,14 +11,4 @@ ...@@ -12,14 +11,4 @@
android:layout_height="40dp" android:layout_height="40dp"
app:roundedCornerRadius="2dp" /> app:roundedCornerRadius="2dp" />
<!-- TODO define the correct bg color for this-->
<ImageView
android:id="@+id/image_unknown_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_help_black_24dp"
android:tint="@color/colorAccent"
android:visibility="gone"
tools:ignore="contentDescription" />
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -2,10 +2,21 @@ ...@@ -2,10 +2,21 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
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"> android:orientation="vertical">
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator"
tools:visibility="visible" />
<FrameLayout <FrameLayout
android:id="@+id/message_list_container" android:id="@+id/message_list_container"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -17,24 +28,31 @@ ...@@ -17,24 +28,31 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="vertical" /> android:scrollbars="vertical" />
</FrameLayout> </FrameLayout>
<View
android:id="@+id/view_dim"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/layout_message_composer"
android:background="@color/colorDim"
android:visibility="gone" />
<include
android:id="@+id/layout_message_attachment_options"
layout="@layout/message_attachment_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/layout_message_composer"
android:layout_margin="5dp"
android:visibility="gone"
tools:visibility="visible" />
<include <include
android:id="@+id/layout_message_composer" android:id="@+id/layout_message_composer"
layout="@layout/message_composer" layout="@layout/message_composer"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
<com.wang.avi.AVLoadingIndicatorView </RelativeLayout>
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator"
tools:visibility="visible" />
</RelativeLayout>
\ No newline at end of file
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
app:layout_constraintLeft_toRightOf="@+id/layout_avatar"> app:layout_constraintLeft_toRightOf="@+id/layout_avatar">
<TextView <TextView
android:id="@+id/text_user_name" android:id="@+id/text_sender"
style="@style/TextAppearance.AppCompat.Title" style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fresco="http://schemas.android.com/apk/res-auto" xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/attachment_container" android:id="@+id/attachment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<TextView
android:id="@+id/text_file_attachment"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/style_message_file_upload_content_bg"
android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_files_24dp"
android:ellipsize="end"
android:gravity="center"
android:maxLength="20"
android:maxLines="1"
android:padding="5dp"
android:visibility="gone"
tools:text="brazilian_anthem.mp4"
tools:visibility="visible" />
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_attachment" android:id="@+id/image_attachment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="150dp" android:layout_height="150dp"
android:visibility="gone"
fresco:actualImageScaleType="fitStart" fresco:actualImageScaleType="fitStart"
fresco:placeholderImage="@drawable/image_dummy" fresco:placeholderImage="@drawable/image_dummy"
tools:visibility="visible" tools:visibility="visible" />
android:visibility="gone"/>
<FrameLayout <FrameLayout
android:id="@+id/audio_video_attachment" android:id="@+id/audio_video_attachment"
...@@ -23,17 +39,18 @@ ...@@ -23,17 +39,18 @@
android:background="@color/black" android:background="@color/black"
android:visibility="gone" android:visibility="gone"
tools:visibility="gone"> tools:visibility="gone">
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@drawable/exo_controls_play"/> android:src="@drawable/exo_controls_play" />
</FrameLayout> </FrameLayout>
<TextView <TextView
android:id="@+id/file_name" android:id="@+id/file_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Filename.png"/> tools:text="Filename.png" />
</LinearLayout> </LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/style_attachment_options"
android:orientation="vertical">
<Button
android:id="@+id/button_files"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="20dp"
android:drawableStart="@drawable/ic_files_24dp"
android:gravity="start|center"
android:text="@string/action_files" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<View <View
...@@ -22,36 +22,51 @@ ...@@ -22,36 +22,51 @@
app:layout_constraintTop_toBottomOf="@+id/divider" /> app:layout_constraintTop_toBottomOf="@+id/divider" />
<LinearLayout <LinearLayout
android:id="@+id/top_container" android:id="@+id/input_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins" android:layout_marginEnd="10dp"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins" android:layout_marginStart="10dp"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingBottom="10dp" android:paddingBottom="10dp"
app:layout_constraintTop_toBottomOf="@+id/divider"> app:layout_constraintTop_toBottomOf="@+id/divider">
<ImageButton
android:id="@+id/button_add_reaction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/msg_content_description_show_attachment_options"
android:src="@drawable/ic_reaction_24dp" />
<EditText <EditText
android:id="@+id/text_message" android:id="@+id/text_message"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:hint="@string/msg_message" android:hint="@string/msg_message"
android:maxLines="4" /> android:maxLines="4" />
<TextView <ImageButton
android:id="@+id/text_send" android:id="@+id/button_show_attachment_options"
style="@style/TextAppearance.AppCompat.Medium" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/msg_content_description_show_attachment_options"
android:src="@drawable/ic_add_24dp" />
<ImageButton
android:id="@+id/button_send"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true" android:contentDescription="@string/msg_content_description_send_message"
android:focusable="true" android:src="@drawable/ic_send_24dp"
android:text="@string/action_send" android:visibility="gone" />
android:textColor="@color/colorAccent" />
</LinearLayout> </LinearLayout>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
\ No newline at end of file
...@@ -11,16 +11,15 @@ ...@@ -11,16 +11,15 @@
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Conectar</string> <string name="action_connect">Conectar</string>
<string name="action_send">Enviar</string>
<string name="action_terms_of_service">Termos de Serviço</string> <string name="action_terms_of_service">Termos de Serviço</string>
<string name="action_privacy_policy">Política de Privacidade</string> <string name="action_privacy_policy">Política de Privacidade</string>
<string name="action_search">Pesquisar</string> <string name="action_search">Pesquisar</string>
<string name="action_update">Atualizar</string> <string name="action_update">Atualizar</string>
<string name="action_logout">Sair</string> <string name="action_logout">Sair</string>
<string name="action_files">Arquivos</string>
<!-- Regular information messages --> <!-- Regular information messages -->
<string name="msg_no_internet_connection">Sem conexão à internet</string> <string name="msg_no_internet_connection">Sem conexão à internet</string>
<string name="msg_invalid_server_url">URL de servidor inválida</string>
<string name="msg_generic_error">Desculpe, ocorreu um erro, tente novamente</string> <string name="msg_generic_error">Desculpe, ocorreu um erro, tente novamente</string>
<string name="msg_no_data_to_display">Nenhum dado para exibir</string> <string name="msg_no_data_to_display">Nenhum dado para exibir</string>
<string name="msg_profile_update_successfully">Perfil atualizado com sucesso</string> <string name="msg_profile_update_successfully">Perfil atualizado com sucesso</string>
...@@ -33,10 +32,12 @@ ...@@ -33,10 +32,12 @@
<string name="msg_new_user">Novo usuário? %1$s</string> <string name="msg_new_user">Novo usuário? %1$s</string>
<string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string> <string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string>
<string name="msg_2fa_code">Código 2FA</string> <string name="msg_2fa_code">Código 2FA</string>
<string name="msg_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_yesterday">ontem</string> <string name="msg_yesterday">ontem</string>
<string name="msg_message">Messagem</string> <string name="msg_message">Messagem</string>
<string name="msg_this_room_is_read_only">Este chat é apenas de leitura</string> <string name="msg_this_room_is_read_only">Este chat é apenas de leitura</string>
<string name="msg_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_invalid_file">Arquivo inválido</string>
<string name="msg_invalid_server_url">URL de servidor inválida</string>
<string name="msg_content_description_log_in_using_facebook">Fazer login através do Facebook</string> <string name="msg_content_description_log_in_using_facebook">Fazer login através do Facebook</string>
<string name="msg_content_description_log_in_using_github">Fazer login através do Github</string> <string name="msg_content_description_log_in_using_github">Fazer login através do Github</string>
<string name="msg_content_description_log_in_using_google">Fazer login através do Google</string> <string name="msg_content_description_log_in_using_google">Fazer login através do Google</string>
...@@ -44,6 +45,8 @@ ...@@ -44,6 +45,8 @@
<string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string> <string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string> <string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string>
<string name="msg_content_description_send_message">Enviar mensagem</string>
<string name="msg_content_description_show_attachment_options">Mostrar opções de anexo</string>
<string name="msg_you">Você</string> <string name="msg_you">Você</string>
<string name="msg_unknown">Desconhecido</string> <string name="msg_unknown">Desconhecido</string>
......
...@@ -15,6 +15,13 @@ ...@@ -15,6 +15,13 @@
<color name="colorDividerMessageComposer">#D8D8D8</color> <color name="colorDividerMessageComposer">#D8D8D8</color>
<color name="colorLightTheme">#FBFBFB</color>
<color name="colorDim">#99000000</color>
<color name="colorBackgroundFileAttachment">#E2FFC8</color>
<color name="colorBackgroundContentFileAttachment">#FFD8F5C0</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="red">#FFFF0000</color> <color name="red">#FFFF0000</color>
...@@ -24,4 +31,4 @@ ...@@ -24,4 +31,4 @@
<color name="linkTextColor">#FF074481</color> <color name="linkTextColor">#FF074481</color>
<color name="linkBackgroundColor">#30074481</color> <color name="linkBackgroundColor">#30074481</color>
</resources> </resources>
\ No newline at end of file
...@@ -12,16 +12,15 @@ ...@@ -12,16 +12,15 @@
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Connect</string> <string name="action_connect">Connect</string>
<string name="action_send">Send</string>
<string name="action_terms_of_service">Terms of Service</string> <string name="action_terms_of_service">Terms of Service</string>
<string name="action_privacy_policy">Privacy Policy</string> <string name="action_privacy_policy">Privacy Policy</string>
<string name="action_search">Search</string> <string name="action_search">Search</string>
<string name="action_update">Update</string> <string name="action_update">Update</string>
<string name="action_logout">Log Out</string> <string name="action_logout">Log Out</string>
<string name="action_files">Files</string>
<!-- Regular information messages --> <!-- Regular information messages -->
<string name="msg_no_internet_connection">No internet connection</string> <string name="msg_no_internet_connection">No internet connection</string>
<string name="msg_invalid_server_url">Invalid server URL</string>
<string name="msg_generic_error">Sorry, an error has occurred, please try again</string> <string name="msg_generic_error">Sorry, an error has occurred, please try again</string>
<string name="msg_no_data_to_display">No data to display</string> <string name="msg_no_data_to_display">No data to display</string>
<string name="msg_profile_update_successfully">Profile update successfully</string> <string name="msg_profile_update_successfully">Profile update successfully</string>
...@@ -34,11 +33,13 @@ ...@@ -34,11 +33,13 @@
<string name="msg_new_user">New user? %1$s</string> <string name="msg_new_user">New user? %1$s</string>
<string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string> <string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string>
<string name="msg_2fa_code">2FA Code</string> <string name="msg_2fa_code">2FA Code</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string>
<string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string> <string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string>
<string name="msg_yesterday">Yesterday</string> <string name="msg_yesterday">Yesterday</string>
<string name="msg_message">Message</string> <string name="msg_message">Message</string>
<string name="msg_this_room_is_read_only">This room is read only</string> <string name="msg_this_room_is_read_only">This room is read only</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string>
<string name="msg_invalid_file">Invalid file</string>
<string name="msg_invalid_server_url">Invalid server URL</string>
<string name="msg_content_description_log_in_using_facebook">Login using Facebook</string> <string name="msg_content_description_log_in_using_facebook">Login using Facebook</string>
<string name="msg_content_description_log_in_using_github">Login using Github</string> <string name="msg_content_description_log_in_using_github">Login using Github</string>
<string name="msg_content_description_log_in_using_google">Login using Google</string> <string name="msg_content_description_log_in_using_google">Login using Google</string>
...@@ -46,6 +47,8 @@ ...@@ -46,6 +47,8 @@
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string> <string name="msg_content_description_log_in_using_meteor">Login using Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string> <string name="msg_content_description_log_in_using_twitter">Login using Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_content_description_send_message">Send message</string>
<string name="msg_content_description_show_attachment_options">Show attachment options</string>
<string name="msg_you">You</string> <string name="msg_you">You</string>
<string name="msg_unknown">Unknown</string> <string name="msg_unknown">Unknown</string>
......
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