Commit 3715659e authored by Lucio Maciel's avatar Lucio Maciel

Merge branch 'develop' into new/generic-file-attachment

parents 7fae0085 380d3688
...@@ -104,7 +104,6 @@ dependencies { ...@@ -104,7 +104,6 @@ dependencies {
implementation libraries.frescoImageViewer implementation libraries.frescoImageViewer
implementation libraries.markwon implementation libraries.markwon
implementation libraries.markwonImageLoader
implementation libraries.sheetMenu implementation libraries.sheetMenu
......
...@@ -207,6 +207,31 @@ class LoginPresenter @Inject constructor( ...@@ -207,6 +207,31 @@ class LoginPresenter @Inject constructor(
} }
} }
getCustomOauthServices(services).let {
for (service in it) {
val serviceName = getCustomOauthServiceName(service)
val customOauthUrl = OauthHelper.getCustomOauthUrl(
getCustomOauthHost(service),
getCustomOauthAuthorizePath(service),
getCustomOauthClientId(service),
currentServer,
serviceName,
state,
getCustomOauthScope(service)
)
view.addCustomOauthServiceButton(
customOauthUrl,
state,
serviceName,
getCustomOauthServiceNameColor(service),
getCustomOauthButtonColor(service)
)
totalSocialAccountsEnabled++
}
}
if (totalSocialAccountsEnabled > 0) { if (totalSocialAccountsEnabled > 0) {
view.enableOauthView() view.enableOauthView()
if (totalSocialAccountsEnabled > 3) { if (totalSocialAccountsEnabled > 3) {
...@@ -299,6 +324,38 @@ class LoginPresenter @Inject constructor( ...@@ -299,6 +324,38 @@ class LoginPresenter @Inject constructor(
}.toString() }.toString()
} }
private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> {
return listMap.filter { map -> map["custom"] == true }
}
private fun getCustomOauthHost(service: Map<String, Any>): String {
return service["serverURL"].toString()
}
private fun getCustomOauthAuthorizePath(service: Map<String, Any>): String {
return service["authorizePath"].toString()
}
private fun getCustomOauthClientId(service: Map<String, Any>): String {
return service["clientId"].toString()
}
private fun getCustomOauthServiceName(service: Map<String, Any>): String {
return service["service"].toString()
}
private fun getCustomOauthScope(service: Map<String, Any>): String {
return service["scope"].toString()
}
private fun getCustomOauthButtonColor(service: Map<String, Any>): Int {
return service["buttonColor"].toString().parseColor()
}
private fun getCustomOauthServiceNameColor(service: Map<String, Any>): Int {
return service["buttonLabelColor"].toString().parseColor()
}
private suspend fun saveAccount(username: String) { private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
......
package chat.rocket.android.authentication.login.presentation package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.server.presentation.VersionCheckView
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
...@@ -75,7 +74,7 @@ interface LoginView : LoadingView, MessageView { ...@@ -75,7 +74,7 @@ interface LoginView : LoadingView, MessageView {
* Enables and shows the oauth view if there is login via social accounts enabled by the server settings. * Enables and shows the oauth view if there is login via social accounts enabled by the server settings.
* *
* REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle], * REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter] or [enableLoginByGitlab]) for the oauth view. * [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter], [enableLoginByGitlab] or [addCustomOauthServiceButton]) for the oauth view.
* If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s). * If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s).
*/ */
fun enableOauthView() fun enableOauthView()
...@@ -178,6 +177,24 @@ interface LoginView : LoadingView, MessageView { ...@@ -178,6 +177,24 @@ interface LoginView : LoadingView, MessageView {
*/ */
fun setupGitlabButtonListener(gitlabUrl: String, state: String) fun setupGitlabButtonListener(gitlabUrl: String, state: String)
/**
* Adds a custom OAuth button in the oauth view.
*
* @customOauthUrl The custom OAuth url to sets up the button (the listener).
* @state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
* @serviceName The custom OAuth service name.
* @serviceNameColor The custom OAuth service name color (just stylizing).
* @buttonColor The color of the custom OAuth button (just stylizing).
* @see [enableOauthView]
*/
fun addCustomOauthServiceButton(
customOauthUrl: String,
state: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
)
/** /**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)). * Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
*/ */
......
...@@ -2,8 +2,8 @@ package chat.rocket.android.authentication.login.ui ...@@ -2,8 +2,8 @@ package chat.rocket.android.authentication.login.ui
import DrawableHelper import DrawableHelper
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.graphics.PorterDuff
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
...@@ -12,10 +12,12 @@ import android.view.LayoutInflater ...@@ -12,10 +12,12 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.Button
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.ScrollView import android.widget.ScrollView
import androidx.core.view.isVisible
import androidx.core.view.postDelayed import androidx.core.view.postDelayed
import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.login.presentation.LoginPresenter import chat.rocket.android.authentication.login.presentation.LoginPresenter
...@@ -37,7 +39,8 @@ internal const val REQUEST_CODE_FOR_CAS = 1 ...@@ -37,7 +39,8 @@ internal const val REQUEST_CODE_FOR_CAS = 1
internal const val REQUEST_CODE_FOR_OAUTH = 2 internal const val REQUEST_CODE_FOR_OAUTH = 2
class LoginFragment : Fragment(), LoginView { class LoginFragment : Fragment(), LoginView {
@Inject lateinit var presenter: LoginPresenter @Inject
lateinit var presenter: LoginPresenter
private var isOauthViewEnable = false private var isOauthViewEnable = false
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
areLoginOptionsNeeded() areLoginOptionsNeeded()
...@@ -61,7 +64,11 @@ class LoginFragment : Fragment(), LoginView { ...@@ -61,7 +64,11 @@ class LoginFragment : Fragment(), LoginView {
deepLinkInfo = arguments?.getParcelable(DEEP_LINK_INFO) deepLinkInfo = arguments?.getParcelable(DEEP_LINK_INFO)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
container?.inflate(R.layout.fragment_authentication_log_in) container?.inflate(R.layout.fragment_authentication_log_in)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...@@ -94,7 +101,10 @@ class LoginFragment : Fragment(), LoginView { ...@@ -94,7 +101,10 @@ class LoginFragment : Fragment(), LoginView {
} }
} else if (requestCode == REQUEST_CODE_FOR_OAUTH) { } else if (requestCode == REQUEST_CODE_FOR_OAUTH) {
data?.apply { data?.apply {
presenter.authenticateWithOauth(getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN), getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)) presenter.authenticateWithOauth(
getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN),
getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
)
} }
} }
} }
...@@ -102,25 +112,29 @@ class LoginFragment : Fragment(), LoginView { ...@@ -102,25 +112,29 @@ class LoginFragment : Fragment(), LoginView {
private fun tintEditTextDrawableStart() { private fun tintEditTextDrawableStart() {
ui { ui {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, it) val personDrawable =
DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, it)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, it) val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, it)
val drawables = arrayOf(personDrawable, lockDrawable) val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables) DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, it, R.color.colorDrawableTintGrey) DrawableHelper.tintDrawables(drawables, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_username_or_email, text_password), drawables) DrawableHelper.compoundDrawables(
arrayOf(text_username_or_email, text_password),
drawables
)
} }
} }
override fun showLoading() { override fun showLoading() {
ui { ui {
view_loading.setVisible(true) view_loading.isVisible = true
} }
} }
override fun hideLoading() { override fun hideLoading() {
ui { ui {
view_loading.setVisible(false) view_loading.isVisible = false
} }
} }
...@@ -142,23 +156,25 @@ class LoginFragment : Fragment(), LoginView { ...@@ -142,23 +156,25 @@ class LoginFragment : Fragment(), LoginView {
override fun showFormView() { override fun showFormView() {
ui { ui {
text_username_or_email.setVisible(true) text_username_or_email.isVisible = true
text_password.setVisible(true) text_password.isVisible = true
} }
} }
override fun hideFormView() { override fun hideFormView() {
ui { ui {
text_username_or_email.setVisible(false) text_username_or_email.isVisible = false
text_password.setVisible(false) text_password.isVisible = false
} }
} }
override fun setupLoginButtonListener() { override fun setupLoginButtonListener() {
ui { ui {
button_log_in.setOnClickListener { button_log_in.setOnClickListener {
presenter.authenticateWithUserAndPassword(text_username_or_email.textContent, presenter.authenticateWithUserAndPassword(
text_password.textContent) text_username_or_email.textContent,
text_password.textContent
)
} }
} }
} }
...@@ -181,21 +197,23 @@ class LoginFragment : Fragment(), LoginView { ...@@ -181,21 +197,23 @@ class LoginFragment : Fragment(), LoginView {
override fun showCasButton() { override fun showCasButton() {
ui { ui {
button_cas.setVisible(true) button_cas.isVisible = true
} }
} }
override fun hideCasButton() { override fun hideCasButton() {
ui { ui {
button_cas.setVisible(false) button_cas.isVisible = false
} }
} }
override fun setupCasButtonListener(casUrl: String, casToken: String) { override fun setupCasButtonListener(casUrl: String, casToken: String) {
ui { activity -> ui { activity ->
button_cas.setOnClickListener { button_cas.setOnClickListener {
startActivityForResult(activity.casWebViewIntent(casUrl, casToken), startActivityForResult(
REQUEST_CODE_FOR_CAS) activity.casWebViewIntent(casUrl, casToken),
REQUEST_CODE_FOR_CAS
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
} }
...@@ -203,7 +221,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -203,7 +221,7 @@ class LoginFragment : Fragment(), LoginView {
override fun showSignUpView() { override fun showSignUpView() {
ui { ui {
text_new_to_rocket_chat.setVisible(true) text_new_to_rocket_chat.isVisible = true
} }
} }
...@@ -224,7 +242,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -224,7 +242,7 @@ class LoginFragment : Fragment(), LoginView {
override fun hideSignUpView() { override fun hideSignUpView() {
ui { ui {
text_new_to_rocket_chat.setVisible(false) text_new_to_rocket_chat.isVisible = false
} }
} }
...@@ -232,26 +250,26 @@ class LoginFragment : Fragment(), LoginView { ...@@ -232,26 +250,26 @@ class LoginFragment : Fragment(), LoginView {
ui { ui {
isOauthViewEnable = true isOauthViewEnable = true
showThreeSocialAccountsMethods() showThreeSocialAccountsMethods()
social_accounts_container.setVisible(true) social_accounts_container.isVisible = true
} }
} }
override fun disableOauthView() { override fun disableOauthView() {
ui { ui {
isOauthViewEnable = false isOauthViewEnable = false
social_accounts_container.setVisible(false) social_accounts_container.isVisible = false
} }
} }
override fun showLoginButton() { override fun showLoginButton() {
ui { ui {
button_log_in.setVisible(true) button_log_in.isVisible = true
} }
} }
override fun hideLoginButton() { override fun hideLoginButton() {
ui { ui {
button_log_in.setVisible(false) button_log_in.isVisible = false
} }
} }
...@@ -264,7 +282,10 @@ class LoginFragment : Fragment(), LoginView { ...@@ -264,7 +282,10 @@ class LoginFragment : Fragment(), LoginView {
override fun setupFacebookButtonListener(facebookOauthUrl: String, state: String) { override fun setupFacebookButtonListener(facebookOauthUrl: String, state: String) {
ui { activity -> ui { activity ->
button_facebook.setOnClickListener { button_facebook.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(facebookOauthUrl, state), REQUEST_CODE_FOR_OAUTH) startActivityForResult(
activity.oauthWebViewIntent(facebookOauthUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
} }
...@@ -279,7 +300,10 @@ class LoginFragment : Fragment(), LoginView { ...@@ -279,7 +300,10 @@ class LoginFragment : Fragment(), LoginView {
override fun setupGithubButtonListener(githubUrl: String, state: String) { override fun setupGithubButtonListener(githubUrl: String, state: String) {
ui { activity -> ui { activity ->
button_github.setOnClickListener { button_github.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(githubUrl, state), REQUEST_CODE_FOR_OAUTH) startActivityForResult(
activity.oauthWebViewIntent(githubUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
} }
...@@ -291,11 +315,15 @@ class LoginFragment : Fragment(), LoginView { ...@@ -291,11 +315,15 @@ class LoginFragment : Fragment(), LoginView {
} }
} }
// TODO: Use custom tabs instead of web view. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968 // TODO: Use custom tabs instead of web view.
// See https://github.com/RocketChat/Rocket.Chat.Android/issues/968
override fun setupGoogleButtonListener(googleUrl: String, state: String) { override fun setupGoogleButtonListener(googleUrl: String, state: String) {
ui { activity -> ui { activity ->
button_google.setOnClickListener { button_google.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(googleUrl, state), REQUEST_CODE_FOR_OAUTH) startActivityForResult(
activity.oauthWebViewIntent(googleUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
} }
...@@ -310,7 +338,10 @@ class LoginFragment : Fragment(), LoginView { ...@@ -310,7 +338,10 @@ class LoginFragment : Fragment(), LoginView {
override fun setupLinkedinButtonListener(linkedinUrl: String, state: String) { override fun setupLinkedinButtonListener(linkedinUrl: String, state: String) {
ui { activity -> ui { activity ->
button_linkedin.setOnClickListener { button_linkedin.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(linkedinUrl, state), REQUEST_CODE_FOR_OAUTH) startActivityForResult(
activity.oauthWebViewIntent(linkedinUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
} }
...@@ -337,7 +368,31 @@ class LoginFragment : Fragment(), LoginView { ...@@ -337,7 +368,31 @@ class LoginFragment : Fragment(), LoginView {
override fun setupGitlabButtonListener(gitlabUrl: String, state: String) { override fun setupGitlabButtonListener(gitlabUrl: String, state: String) {
ui { activity -> ui { activity ->
button_gitlab.setOnClickListener { button_gitlab.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(gitlabUrl, state), REQUEST_CODE_FOR_OAUTH) startActivityForResult(
activity.oauthWebViewIntent(gitlabUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun addCustomOauthServiceButton(
customOauthUrl: String,
state: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
) {
ui { activity ->
val button = getCustomOauthButton(serviceName, serviceNameColor, buttonColor)
social_accounts_container.addView(button)
button.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(customOauthUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
} }
...@@ -345,7 +400,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -345,7 +400,7 @@ class LoginFragment : Fragment(), LoginView {
override fun setupFabListener() { override fun setupFabListener() {
ui { ui {
button_fab.setVisible(true) button_fab.isVisible = true
button_fab.setOnClickListener({ button_fab.setOnClickListener({
button_fab.hide() button_fab.hide()
showRemainingSocialAccountsView() showRemainingSocialAccountsView()
...@@ -355,8 +410,9 @@ class LoginFragment : Fragment(), LoginView { ...@@ -355,8 +410,9 @@ class LoginFragment : Fragment(), LoginView {
} }
override fun setupGlobalListener() { override fun setupGlobalListener() {
// We need to setup the layout to hide and show the oauth interface when the soft keyboard is shown // We need to setup the layout to hide and show the oauth interface when the soft keyboard
// (means that the user touched the text_username_or_email or text_password EditText to fill that respective fields). // is shown (which means that the user has touched the text_username_or_email or
// text_password EditText to fill that respective fields).
if (!isGlobalLayoutListenerSetUp) { if (!isGlobalLayoutListenerSetUp) {
scroll_view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener) scroll_view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
isGlobalLayoutListenerSetUp = true isGlobalLayoutListenerSetUp = true
...@@ -385,7 +441,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -385,7 +441,7 @@ class LoginFragment : Fragment(), LoginView {
(0..social_accounts_container.childCount) (0..social_accounts_container.childCount)
.mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton } .mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton }
.filter { it.isClickable } .filter { it.isClickable }
.forEach { it.setVisible(true) } .forEach { it.isVisible = true }
} }
} }
} }
...@@ -422,19 +478,64 @@ class LoginFragment : Fragment(), LoginView { ...@@ -422,19 +478,64 @@ class LoginFragment : Fragment(), LoginView {
.mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton } .mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton }
.filter { it.isClickable } .filter { it.isClickable }
.take(3) .take(3)
.forEach { it.setVisible(true) } .forEach { it.isVisible = true }
} }
private fun showOauthView() { private fun showOauthView() {
if (isOauthViewEnable) { if (isOauthViewEnable) {
social_accounts_container.setVisible(true) social_accounts_container.isVisible = true
if (enabledSocialAccounts() > 3) {
button_fab.isVisible = true
}
} }
} }
private fun hideOauthView() { private fun hideOauthView() {
if (isOauthViewEnable) { if (isOauthViewEnable) {
social_accounts_container.setVisible(false) social_accounts_container.isVisible = false
button_fab.setVisible(false) button_fab.isVisible = false
} }
} }
private fun enabledSocialAccounts(): Int {
return enabledOauthAccountsImageButtons() + enabledServicesAccountsButtons()
}
private fun enabledOauthAccountsImageButtons(): Int {
return (0..social_accounts_container.childCount)
.mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton }
.filter { it.isClickable }
.size
}
private fun enabledServicesAccountsButtons(): Int {
return (0..social_accounts_container.childCount)
.mapNotNull { social_accounts_container.getChildAt(it) as? Button }
.size
}
/**
* Gets a stylized custom OAuth button.
*/
private fun getCustomOauthButton(
buttonText: String,
buttonTextColor: Int,
buttonBgColor: Int
): Button {
val params: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val margin = resources.getDimensionPixelSize(R.dimen.screen_edge_left_and_right_margins)
params.setMargins(margin, margin, margin, 0)
val button = Button(context)
button.layoutParams = params
button.text = buttonText
button.setTextColor(buttonTextColor)
button.background.setColorFilter(buttonBgColor, PorterDuff.Mode.MULTIPLY)
return button
}
} }
\ No newline at end of file
...@@ -180,15 +180,15 @@ class ChatRoomAdapter( ...@@ -180,15 +180,15 @@ class ChatRoomAdapter(
} }
} }
val actionsListener = object : BaseViewHolder.ActionsListener { private val actionsListener = object : BaseViewHolder.ActionsListener {
override fun isActionsEnabled(): Boolean = enableActions override fun isActionsEnabled(): Boolean = enableActions
override fun onActionSelected(item: MenuItem, message: Message) { override fun onActionSelected(item: MenuItem, message: Message) {
message.apply { message.apply {
when (item.itemId) { when (item.itemId) {
R.id.action_menu_msg_delete -> presenter?.deleteMessage(roomId, id) R.id.action_menu_msg_delete -> presenter?.deleteMessage(roomId, id)
R.id.action_menu_msg_quote -> presenter?.citeMessage(roomType, roomName, id, false) R.id.action_menu_msg_quote -> presenter?.citeMessage(roomType, id, false)
R.id.action_menu_msg_reply -> presenter?.citeMessage(roomType, roomName, id, true) R.id.action_menu_msg_reply -> presenter?.citeMessage(roomType, id, true)
R.id.action_menu_msg_copy -> presenter?.copyMessage(id) R.id.action_menu_msg_copy -> presenter?.copyMessage(id)
R.id.action_menu_msg_edit -> presenter?.editMessage(roomId, id, message.message) R.id.action_menu_msg_edit -> presenter?.editMessage(roomId, id, message.message)
R.id.action_menu_msg_pin_unpin -> { R.id.action_menu_msg_pin_unpin -> {
......
...@@ -31,7 +31,6 @@ import chat.rocket.core.internal.rest.* ...@@ -31,7 +31,6 @@ import chat.rocket.core.internal.rest.*
import chat.rocket.core.model.Command import chat.rocket.core.model.Command
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import chat.rocket.core.model.Value
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async import kotlinx.coroutines.experimental.async
...@@ -59,10 +58,11 @@ class ChatRoomPresenter @Inject constructor( ...@@ -59,10 +58,11 @@ class ChatRoomPresenter @Inject constructor(
private val mapper: ViewModelMapper, private val mapper: ViewModelMapper,
private val jobSchedulerInteractor: JobSchedulerInteractor private val jobSchedulerInteractor: JobSchedulerInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val manager = factory.create(currentServer) private val manager = factory.create(currentServer)
private val client = manager.client private val client = manager.client
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!)
private val messagesChannel = Channel<Message>() private val messagesChannel = Channel<Message>()
private var chatRoomId: String? = null private var chatRoomId: String? = null
...@@ -194,7 +194,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -194,7 +194,7 @@ class ChatRoomPresenter @Inject constructor(
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error uploading file") Timber.d(ex, "Error uploading file")
when(ex) { when (ex) {
is RocketChatException -> view.showMessage(ex) is RocketChatException -> view.showMessage(ex)
else -> view.showGenericErrorMessage() else -> view.showGenericErrorMessage()
} }
...@@ -279,7 +279,6 @@ class ChatRoomPresenter @Inject constructor( ...@@ -279,7 +279,6 @@ class ChatRoomPresenter @Inject constructor(
// TODO - we need to better treat connection problems here, but no let gaps // TODO - we need to better treat connection problems here, but no let gaps
// on the messages list // on the messages list
Timber.d(ex, "Error fetching channel history") Timber.d(ex, "Error fetching channel history")
ex.printStackTrace()
} }
} }
} }
...@@ -326,43 +325,37 @@ class ChatRoomPresenter @Inject constructor( ...@@ -326,43 +325,37 @@ class ChatRoomPresenter @Inject constructor(
* Quote or reply a message. * Quote or reply a message.
* *
* @param roomType The current room type. * @param roomType The current room type.
* @param roomName The name of the current room.
* @param messageId The id of the message to make citation for. * @param messageId The id of the message to make citation for.
* @param mentionAuthor true means the citation is a reply otherwise it's a quote. * @param mentionAuthor true means the citation is a reply otherwise it's a quote.
*/ */
fun citeMessage(roomType: String, roomName: String, messageId: String, mentionAuthor: Boolean) { fun citeMessage(roomType: String, messageId: String, mentionAuthor: Boolean) {
launchUI(strategy) { launchUI(strategy) {
val message = messagesRepository.getById(messageId) val message = messagesRepository.getById(messageId)
val me: Myself? = try { val me: Myself? = try {
retryIO("me()") { client.me() } //TODO: Cache this and use an interactor retryIO("me()") { client.me() } //TODO: Cache this and use an interactor
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error getting myself info.") Timber.e(ex)
ex.printStackTrace()
null null
} }
message?.let { m -> message?.let { msg ->
val id = m.id val id = msg.id
val username = m.sender?.username val username = msg.sender?.username ?: ""
val user = "@" + if (settings.useRealName()) m.sender?.name val mention = if (mentionAuthor && me?.username != username) "@$username" else ""
?: m.sender?.username else m.sender?.username val room = if (roomTypeOf(roomType) is RoomType.DirectMessage) username else roomType
val mention = if (mentionAuthor && me?.username != username) user else ""
val type = roomTypeOf(roomType)
val room = when (type) {
is RoomType.Channel -> "channel"
is RoomType.DirectMessage -> "direct"
is RoomType.PrivateGroup -> "group"
is RoomType.Livechat -> "livechat"
is RoomType.Custom -> "custom" //TODO: put appropriate callback string here.
}
view.showReplyingAction( view.showReplyingAction(
username = user, username = getDisplayName(msg.sender),
replyMarkdown = "[ ]($currentServer/$room/$roomName?msg=$id) $mention ", replyMarkdown = "[ ]($currentServer/$roomType/$room?msg=$id) $mention ",
quotedMessage = mapper.map(message).last().preview?.message ?: "" quotedMessage = mapper.map(message).last().preview?.message ?: ""
) )
} }
} }
} }
private fun getDisplayName(user: SimpleUser?): String {
val username = user?.username ?: ""
return if (settings.useRealName()) user?.name ?: "@$username" else "@$username"
}
/** /**
* Copy message to clipboard. * Copy message to clipboard.
* *
......
...@@ -5,7 +5,6 @@ import android.content.Context ...@@ -5,7 +5,6 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.text.Html
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
...@@ -32,21 +31,21 @@ import okhttp3.HttpUrl ...@@ -32,21 +31,21 @@ import okhttp3.HttpUrl
import java.security.InvalidParameterException import java.security.InvalidParameterException
import javax.inject.Inject import javax.inject.Inject
class ViewModelMapper @Inject constructor(private val context: Context, class ViewModelMapper @Inject constructor(
private val context: Context,
private val parser: MessageParser, private val parser: MessageParser,
private val messagesRepository: MessagesRepository,
private val getAccountInteractor: GetAccountInteractor,
tokenRepository: TokenRepository, tokenRepository: TokenRepository,
serverInteractor: GetCurrentServerInteractor, serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor, getSettingsInteractor: GetSettingsInteractor,
localRepository: LocalRepository) { localRepository: LocalRepository
) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val settings: Map<String, Value<Any>> = getSettingsInteractor.get(currentServer) private val settings: Map<String, Value<Any>> = getSettingsInteractor.get(currentServer)
private val baseUrl = settings.baseUrl() private val baseUrl = settings.baseUrl()
private val token = tokenRepository.get(currentServer) private val token = tokenRepository.get(currentServer)
private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
private val secundaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText) private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
suspend fun map(message: Message): List<BaseViewModel<*>> { suspend fun map(message: Message): List<BaseViewModel<*>> {
return translate(message) return translate(message)
...@@ -102,7 +101,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -102,7 +101,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
getReactions(message), preview = message.copy(message = url.url)) getReactions(message), preview = message.copy(message = url.url))
} }
private suspend fun mapAttachment(message: Message, attachment: Attachment): BaseViewModel<*>? { private fun mapAttachment(message: Message, attachment: Attachment): BaseViewModel<*>? {
return when (attachment) { return when (attachment) {
is FileAttachment -> mapFileAttachment(message, attachment) is FileAttachment -> mapFileAttachment(message, attachment)
is MessageAttachment -> mapMessageAttachment(message, attachment) is MessageAttachment -> mapMessageAttachment(message, attachment)
...@@ -112,7 +111,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -112,7 +111,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
} }
} }
private suspend fun mapColorAttachment(message: Message, attachment: ColorAttachment): BaseViewModel<*>? { private fun mapColorAttachment(message: Message, attachment: ColorAttachment): BaseViewModel<*>? {
return with(attachment) { return with(attachment) {
val content = stripMessageQuotes(message) val content = stripMessageQuotes(message)
val id = attachmentId(message, attachment) val id = attachmentId(message, attachment)
...@@ -124,7 +123,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -124,7 +123,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
} }
} }
private suspend fun mapAuthorAttachment(message: Message, attachment: AuthorAttachment): AuthorAttachmentViewModel { private fun mapAuthorAttachment(message: Message, attachment: AuthorAttachment): AuthorAttachmentViewModel {
return with(attachment) { return with(attachment) {
val content = stripMessageQuotes(message) val content = stripMessageQuotes(message)
...@@ -152,7 +151,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -152,7 +151,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
} }
} }
private suspend fun mapMessageAttachment(message: Message, attachment: MessageAttachment): MessageAttachmentViewModel { private fun mapMessageAttachment(message: Message, attachment: MessageAttachment): MessageAttachmentViewModel {
val attachmentAuthor = attachment.author val attachmentAuthor = attachment.author
val time = attachment.timestamp?.let { getTime(it) } val time = attachment.timestamp?.let { getTime(it) }
val attachmentText = when (attachment.attachments.orEmpty().firstOrNull()) { val attachmentText = when (attachment.attachments.orEmpty().firstOrNull()) {
...@@ -239,7 +238,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -239,7 +238,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
isFirstUnread = false, preview = preview, isTemporary = isTemp) isFirstUnread = false, preview = preview, isTemporary = isTemp)
} }
private suspend fun mapMessagePreview(message: Message): Message { private fun mapMessagePreview(message: Message): Message {
return when (message.isSystemMessage()) { return when (message.isSystemMessage()) {
false -> stripMessageQuotes(message) false -> stripMessageQuotes(message)
true -> message.copy(message = getSystemMessage(message).toString()) true -> message.copy(message = getSystemMessage(message).toString())
...@@ -265,7 +264,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -265,7 +264,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return reactions ?: emptyList() return reactions ?: emptyList()
} }
private suspend fun stripMessageQuotes(message: Message): Message { private fun stripMessageQuotes(message: Message): Message {
val baseUrl = settings.baseUrl() val baseUrl = settings.baseUrl()
return message.copy( return message.copy(
message = message.message.replace("\\[[^\\]]+\\]\\($baseUrl[^)]+\\)".toRegex(), "").trim() message = message.message.replace("\\[[^\\]]+\\]\\($baseUrl[^)]+\\)".toRegex(), "").trim()
...@@ -280,7 +279,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -280,7 +279,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
username?.let { username?.let {
append(" ") append(" ")
scale(0.8f) { scale(0.8f) {
color(secundaryTextColor) { color(secondaryTextColor) {
append("@$username") append("@$username")
} }
} }
...@@ -306,7 +305,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -306,7 +305,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
private fun getTime(timestamp: Long) = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(timestamp)) private fun getTime(timestamp: Long) = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(timestamp))
private suspend fun getContent(message: Message): CharSequence { private fun getContent(message: Message): CharSequence {
return when (message.isSystemMessage()) { return when (message.isSystemMessage()) {
true -> getSystemMessage(message) true -> getSystemMessage(message)
false -> parser.renderMarkdown(message, currentUsername) false -> parser.renderMarkdown(message, currentUsername)
......
...@@ -48,10 +48,8 @@ import okhttp3.Interceptor ...@@ -48,10 +48,8 @@ import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import ru.noties.markwon.SpannableConfiguration import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.il.AsyncDrawableLoader
import ru.noties.markwon.spans.SpannableTheme import ru.noties.markwon.spans.SpannableTheme
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Singleton import javax.inject.Singleton
...@@ -265,11 +263,6 @@ class AppModule { ...@@ -265,11 +263,6 @@ class AppModule {
fun provideConfiguration(context: Application, client: OkHttpClient): SpannableConfiguration { fun provideConfiguration(context: Application, client: OkHttpClient): SpannableConfiguration {
val res = context.resources val res = context.resources
return SpannableConfiguration.builder(context) return SpannableConfiguration.builder(context)
.asyncDrawableLoader(AsyncDrawableLoader.builder()
.client(client)
.executorService(Executors.newCachedThreadPool())
.resources(res)
.build())
.theme(SpannableTheme.builder() .theme(SpannableTheme.builder()
.linkColor(res.getColor(R.color.colorAccent)) .linkColor(res.getColor(R.color.colorAccent))
.build()) .build())
...@@ -278,8 +271,9 @@ class AppModule { ...@@ -278,8 +271,9 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideMessageParser(context: Application, configuration: SpannableConfiguration): MessageParser { fun provideMessageParser(context: Application, configuration: SpannableConfiguration, serverInteractor: GetCurrentServerInteractor, settingsInteractor: GetSettingsInteractor): MessageParser {
return MessageParser(context, configuration) val url = serverInteractor.get()!!
return MessageParser(context, configuration, settingsInteractor.get(url))
} }
@Provides @Provides
......
package chat.rocket.android.helper package chat.rocket.android.helper
import android.app.Application import android.app.Application
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.RectF import android.graphics.RectF
import android.net.Uri import android.net.Uri
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import android.provider.Browser
import android.support.v4.content.res.ResourcesCompat import android.support.v4.content.res.ResourcesCompat
import android.text.Spanned import android.text.Spanned
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
...@@ -17,6 +14,8 @@ import android.text.style.ReplacementSpan ...@@ -17,6 +14,8 @@ import android.text.style.ReplacementSpan
import android.util.Patterns import android.util.Patterns
import android.view.View import android.view.View
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.android.widget.emoji.EmojiRepository import chat.rocket.android.widget.emoji.EmojiRepository
import chat.rocket.android.widget.emoji.EmojiTypefaceSpan import chat.rocket.android.widget.emoji.EmojiTypefaceSpan
...@@ -29,10 +28,13 @@ import ru.noties.markwon.Markwon ...@@ -29,10 +28,13 @@ import ru.noties.markwon.Markwon
import ru.noties.markwon.SpannableBuilder import ru.noties.markwon.SpannableBuilder
import ru.noties.markwon.SpannableConfiguration import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.renderer.SpannableMarkdownVisitor import ru.noties.markwon.renderer.SpannableMarkdownVisitor
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MessageParser @Inject constructor(val context: Application, private val configuration: SpannableConfiguration) { class MessageParser @Inject constructor(
private val context: Application,
private val configuration: SpannableConfiguration,
private val settings: PublicSettings
) {
private val parser = Markwon.createParser() private val parser = Markwon.createParser()
...@@ -53,7 +55,7 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -53,7 +55,7 @@ class MessageParser @Inject constructor(val context: Application, private val co
parentNode.accept(LinkVisitor(builder)) parentNode.accept(LinkVisitor(builder))
parentNode.accept(EmojiVisitor(configuration, builder)) parentNode.accept(EmojiVisitor(configuration, builder))
message.mentions?.let { message.mentions?.let {
parentNode.accept(MentionVisitor(context, builder, it, selfUsername)) parentNode.accept(MentionVisitor(context, builder, it, selfUsername, settings))
} }
return builder.text() return builder.text()
...@@ -66,19 +68,27 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -66,19 +68,27 @@ class MessageParser @Inject constructor(val context: Application, private val co
.replace("\\_(.+)\\_".toRegex()) { "_${it.groupValues[1].trim()}_" } .replace("\\_(.+)\\_".toRegex()) { "_${it.groupValues[1].trim()}_" }
} }
class MentionVisitor(context: Context, class MentionVisitor(
context: Context,
private val builder: SpannableBuilder, private val builder: SpannableBuilder,
private val mentions: List<SimpleUser>, private val mentions: List<SimpleUser>,
private val currentUser: String?) : AbstractVisitor() { private val currentUser: String?,
private val settings: PublicSettings
) : AbstractVisitor() {
private val othersTextColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme) private val othersTextColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme)
private val othersBackgroundColor = ResourcesCompat.getColor(context.resources, android.R.color.transparent, context.theme) private val othersBackgroundColor = ResourcesCompat.getColor(context.resources, android.R.color.transparent, context.theme)
private val myselfTextColor = ResourcesCompat.getColor(context.resources, R.color.white, context.theme) private val myselfTextColor = ResourcesCompat.getColor(context.resources, R.color.white, context.theme)
private val myselfBackgroundColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme) private val myselfBackgroundColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme)
private val mentionPadding = context.resources.getDimensionPixelSize(R.dimen.padding_mention).toFloat() private val mentionPadding = context.resources.getDimensionPixelSize(R.dimen.padding_mention).toFloat()
private val mentionRadius = context.resources.getDimensionPixelSize(R.dimen.radius_mention).toFloat() private val mentionRadius = context.resources.getDimensionPixelSize(R.dimen.radius_mention).toFloat()
override fun visit(t: Text) { override fun visit(t: Text) {
val text = t.literal val text = t.literal
val mentionsList = mentions.map { it.username }.toMutableList() val mentionsList = mentions.map {
if (settings.useRealName()) it.name else it.username ?: ""
}.toMutableList()
mentionsList.add("all") mentionsList.add("all")
mentionsList.add("here") mentionsList.add("here")
...@@ -101,8 +111,11 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -101,8 +111,11 @@ class MessageParser @Inject constructor(val context: Application, private val co
} }
} }
class EmojiVisitor(configuration: SpannableConfiguration, private val builder: SpannableBuilder) class EmojiVisitor(
: SpannableMarkdownVisitor(configuration, builder) { configuration: SpannableConfiguration,
private val builder: SpannableBuilder
) : SpannableMarkdownVisitor(configuration, builder) {
override fun visit(document: Document) { override fun visit(document: Document) {
val spannable = EmojiParser.parse(builder.text()) val spannable = EmojiParser.parse(builder.text())
if (spannable is Spanned) { if (spannable is Spanned) {
...@@ -127,7 +140,7 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -127,7 +140,7 @@ class MessageParser @Inject constructor(val context: Application, private val co
if (!link.startsWith("@") && link !in consumed) { if (!link.startsWith("@") && link !in consumed) {
builder.setSpan(object : ClickableSpan() { builder.setSpan(object : ClickableSpan() {
override fun onClick(view: View) { override fun onClick(view: View) {
with (view) { with(view) {
val tabsbuilder = CustomTabsIntent.Builder() val tabsbuilder = CustomTabsIntent.Builder()
tabsbuilder.setToolbarColor(ResourcesCompat.getColor(context.resources, R.color.colorPrimary, context.theme)) tabsbuilder.setToolbarColor(ResourcesCompat.getColor(context.resources, R.color.colorPrimary, context.theme))
val customTabsIntent = tabsbuilder.build() val customTabsIntent = tabsbuilder.build()
...@@ -150,11 +163,14 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -150,11 +163,14 @@ class MessageParser @Inject constructor(val context: Application, private val co
} }
} }
class MentionSpan(private val backgroundColor: Int, class MentionSpan(
private val backgroundColor: Int,
private val textColor: Int, private val textColor: Int,
private val radius: Float, private val radius: Float,
padding: Float, padding: Float,
referSelf: Boolean) : ReplacementSpan() { referSelf: Boolean
) : ReplacementSpan() {
private val padding: Float = if (referSelf) padding else 0F private val padding: Float = if (referSelf) padding else 0F
override fun getSize(paint: Paint, override fun getSize(paint: Paint,
......
...@@ -91,4 +91,34 @@ object OauthHelper { ...@@ -91,4 +91,34 @@ object OauthHelper {
"&response_type=code" + "&response_type=code" +
"&scope=email" "&scope=email"
} }
/**
* Returns the Custom Oauth URL.
*
* @param host The custom OAuth host.
* @param authorizePath The OAuth authorization path.
* @param clientId The custom OAuth client ID.
* @param serverUrl The server URL.
* @param serviceName The service name.
* @param state An unguessable random string used to protect against forgery attacks.
* @param scope The custom OAuth scope.
* @return The Custom Oauth URL.
*/
fun getCustomOauthUrl(
host: String,
authorizePath: String,
clientId: String,
serverUrl: String,
serviceName: String,
state: String,
scope: String
): String {
return host +
authorizePath +
"?client_id=$clientId" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/$serviceName" +
"&state=$state" +
"&scope=$scope" +
"&response_type=code"
}
} }
package chat.rocket.android.util.extensions package chat.rocket.android.util.extensions
import android.graphics.Color
import android.util.Patterns import android.util.Patterns
import timber.log.Timber
fun String.removeTrailingSlash(): String { fun String.removeTrailingSlash(): String {
return if (isNotEmpty() && this[length - 1] == '/') { return if (isNotEmpty() && this[length - 1] == '/') {
...@@ -33,3 +35,13 @@ fun String.termsOfServiceUrl() = "${removeTrailingSlash()}/terms-of-service" ...@@ -33,3 +35,13 @@ fun String.termsOfServiceUrl() = "${removeTrailingSlash()}/terms-of-service"
fun String.privacyPolicyUrl() = "${removeTrailingSlash()}/privacy-policy" fun String.privacyPolicyUrl() = "${removeTrailingSlash()}/privacy-policy"
fun String.isValidUrl(): Boolean = Patterns.WEB_URL.matcher(this).matches() fun String.isValidUrl(): Boolean = Patterns.WEB_URL.matcher(this).matches()
fun String.parseColor(): Int {
return try {
Color.parseColor(this)
} catch (exception: IllegalArgumentException) {
// Log the exception and get the white color.
Timber.e(exception)
Color.parseColor("white")
}
}
\ No newline at end of file
...@@ -15,6 +15,7 @@ import android.view.inputmethod.InputMethodManager ...@@ -15,6 +15,7 @@ import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
// TODO: Remove. Use KTX instead.
fun View.setVisible(visible: Boolean) { fun View.setVisible(visible: Boolean) {
visibility = if (visible) { visibility = if (visible) {
View.VISIBLE View.VISIBLE
......
...@@ -78,6 +78,7 @@ class OauthWebViewActivity : AppCompatActivity() { ...@@ -78,6 +78,7 @@ class OauthWebViewActivity : AppCompatActivity() {
private fun setupWebView() { private fun setupWebView() {
with(web_view.settings) { with(web_view.settings) {
javaScriptEnabled = true javaScriptEnabled = true
domStorageEnabled = true
// TODO Remove this workaround that is required to make Google OAuth to work. We should use Custom Tabs instead. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968 // TODO Remove this workaround that is required to make Google OAuth to work. We should use Custom Tabs instead. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968
if (webPageUrl.contains("google")) { if (webPageUrl.contains("google")) {
userAgentString = "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/43.0.2357.65 Mobile Safari/535.19" userAgentString = "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/43.0.2357.65 Mobile Safari/535.19"
......
<resources> <resources>
<!-- Titles --> <!-- Titles -->
<string name="title_sign_in_your_server">Sign in your server</string> <string name="title_sign_in_your_server">Inicia sesión en tu servidor</string>
<string name="title_log_in">Log in</string> <string name="title_log_in">Iniciar sesión</string>
<string name="title_register_username">Register username</string> <string name="title_register_username">Registrar nombre de usuario</string>
<string name="title_sign_up">Sign up</string> <string name="title_sign_up">Regístrate</string>
<string name="title_authentication">Authentication</string> <string name="title_authentication">Autenticación</string>
<string name="title_legal_terms">Legal Terms</string> <string name="title_legal_terms">Términos legales</string>
<string name="title_chats">Chats</string> <string name="title_chats">Chats</string>
<string name="title_profile">Profile</string> <string name="title_profile">Perfil</string>
<string name="title_members">Members (%d)</string> <string name="title_members">Miembros (%d)</string>
<string name="title_settings">Settings</string> <string name="title_settings">Configuraciones</string>
<string name="title_password">Change Password</string> <string name="title_password">Cambia la contraseña</string>
<string name="title_update_profile">Update profile</string> <string name="title_update_profile">Actualización del perfil</string>
<string name="title_about">About</string> <string name="title_about">Acerca de</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Connect</string> <string name="action_connect">Conectar</string>"'
<string name="action_use_this_username">Use this username</string> <string name="action_use_this_username">Usa este nombre de usuario</string>
<string name="action_login_or_sign_up">Tap this button to log in or create an account</string> <string name="action_login_or_sign_up">Toca en este botón para iniciar sesión o crear una cuenta</string>
<string name="action_terms_of_service">Terms of Service</string> <string name="action_terms_of_service">Términos de Servicio</string>
<string name="action_privacy_policy">Privacy Policy</string> <string name="action_privacy_policy">Política de Privacidad</string>
<string name="action_search">Search</string> <string name="action_search">Buscar</string>
<string name="action_update">Update</string> <string name="action_update">Actualizar</string>
<string name="action_settings">Settings</string> <string name="action_settings">Configuraciones</string>
<string name="action_logout">Logout</string> <string name="action_logout">Cerrar sesión</string>
<string name="action_files">Files</string> <string name="action_files">Archivos</string>
<string name="action_confirm_password">Confirm Password Change</string> <string name="action_confirm_password">Confirmar cambio de contraseña</string>
<string name="action_join_chat">Join Chat</string> <string name="action_join_chat">Unirse al chat</string>
<string name="action_add_account">Add account</string> <string name="action_add_account">Añadir cuenta</string>
<string name="action_online">Online</string> <string name="action_online">Conectado(s)</string>
<string name="action_away">Away</string> <string name="action_away">Ausente</string>
<string name="action_busy">Busy</string> <string name="action_busy">Ocupado</string>
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_password">Change Password</item> <item name="item_password">Cambia la contraseña</item>
<item name="item_password">About</item> <item name="item_password">Acerca de</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
<string name="msg_generic_error">Sorry, an error has occurred, please try again</string> <string name="msg_generic_error">Lo sentimos, ha ocurrido un error, por favor intente de nuevo</string>
<string name="msg_no_data_to_display">No data to display</string> <string name="msg_no_data_to_display">No hay información para mostrar</string>
<string name="msg_profile_update_successfully">Profile update successfully</string> <string name="msg_profile_update_successfully">Actualización de perfil con éxito</string>
<string name="msg_username">username</string> <string name="msg_username">usuario</string>
<string name="msg_username_or_email">username or email</string> <string name="msg_username_or_email">nombre de usuario o correo electrónico</string>
<string name="msg_password">password</string> <string name="msg_password">contraseña</string>
<string name="msg_name">name</string> <string name="msg_name">nombre</string>
<string name="msg_email">email</string> <string name="msg_email">correo electrónico</string>
<string name="msg_avatar_url">avatar URL</string> <string name="msg_avatar_url">URL del avatar</string>
<string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string> <string name="msg_or_continue_using_social_accounts">O continuar usando cuentas sociales</string>
<string name="msg_new_user">New user? %1$s</string> <string name="msg_new_user">Nuevo usuario? %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">Al continuar estás aceptando nuestra\n%1$s y %2$s</string>
<string name="msg_2fa_code">2FA Code</string> <string name="msg_2fa_code">Código 2FA</string>
<string name="msg_yesterday">Yesterday</string> <string name="msg_yesterday">Ayer</string>
<string name="msg_message">Message</string> <string name="msg_message">Mensaje</string>
<string name="msg_this_room_is_read_only">This room is read only</string> <string name="msg_this_room_is_read_only">Esta sala es de solo lectura</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string> <string name="msg_invalid_2fa_code">Código 2FA no válido</string>
<string name="msg_invalid_file">Invalid file</string> <string name="msg_invalid_file">Archivo inválido</string>
<string name="msg_invalid_server_url">Invalid server URL</string> <string name="msg_invalid_server_url">URL del servidor inválido</string>
<string name="msg_content_description_log_in_using_facebook">Login using Facebook</string> <string name="msg_content_description_log_in_using_facebook">Inicia sesión usando Facebook</string>
<string name="msg_content_description_log_in_using_github">Login using Github</string> <string name="msg_content_description_log_in_using_github">Inicia sesión usando Github</string>
<string name="msg_content_description_log_in_using_google">Login using Google</string> <string name="msg_content_description_log_in_using_google">Inicia sesión usando Google</string>
<string name="msg_content_description_log_in_using_linkedin">Login using Linkedin</string> <string name="msg_content_description_log_in_using_linkedin">Inicia sesión usando Linkedin</string>
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string> <string name="msg_content_description_log_in_using_meteor">Inicia sesión usando Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string> <string name="msg_content_description_log_in_using_twitter">Inicia sesión usando Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Inicia sesión usando Gitlab</string>
<string name="msg_content_description_send_message">Send message</string> <string name="msg_content_description_send_message">Enviar mensaje</string>
<string name="msg_content_description_show_attachment_options">Show attachment options</string> <string name="msg_content_description_show_attachment_options">Mostrar opciones de archivo adjunto</string>
<string name="msg_you">You</string> <string name="msg_you"></string>
<string name="msg_unknown">Unknown</string> <string name="msg_unknown">Desconocido</string>
<string name="msg_email_address">E-mail address</string> <string name="msg_email_address">Dirección de correo electrónico</string>
<string name="msg_utc_offset">UTC offset</string> <string name="msg_utc_offset">Desplazamiento UTC</string>
<string name="msg_new_password">Enter New Password</string> <string name="msg_new_password">Introduzca nueva contraseña</string>
<string name="msg_confirm_password">Confirm New Password</string> <string name="msg_confirm_password">Confirmar nueva contraseña</string>
<string name="msg_unread_messages">Unread messages</string> <string name="msg_unread_messages">Mensajes no leídos</string>
<string name="msg_preview_video">Video</string> <string name="msg_preview_video">Vídeo</string>
<string name="msg_preview_audio">Audio</string> <string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Photo</string> <string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">File</string> <string name="msg_preview_file">Archivo</string>
<string name="msg_no_messages_yet">No messages yet</string> <string name="msg_no_messages_yet">Aún no hay mensajes</string>
<string name="msg_version">Version %1$s</string> <string name="msg_version">Versión %1$s</string>
<string name="msg_build">Build %1$d</string> <string name="msg_build">Build %1$d</string>
<string name="msg_ok">OK</string> <string name="msg_ok">OK</string>
<string name="msg_ver_not_recommended"> <string name="msg_ver_not_recommended">
Looks like your server version is below the recommended version %1$s.\nYou can still login but you may experience unexpected behaviors.</string> Parece que la versión de tu servidor está por debajo de la versión recomendada %1$s.\nAún puede iniciar sesión, pero puede experimentar comportamientos inesperados.</string>
<string name="msg_ver_not_minimum"> <string name="msg_ver_not_minimum">
Looks like your server version is below the minimum required version %1$s.\nPlease upgrade your server to login! Parece que la versión del servidor está por debajo de la versión mínima requerida %1$s.\nActualice su servidor para iniciar sesión!
</string> </string>
<string name="msg_proceed">PROCEED</string> <string name="msg_proceed">PROCEDER</string>
<string name="msg_cancel">CANCEL</string> <string name="msg_cancel">CANCELAR</string>
<string name="msg_warning">WARNING</string> <string name="msg_warning">ADVERTENCIA</string>
<string name="msg_http_insecure">When using HTTP, you\'re connecting to an insecure server. We don\'t recommend you doing that.</string> <string name="msg_http_insecure">Al usar HTTP, te estás conectando a un servidor inseguro. No te recomendamos que hagas eso.</string>
<string name="msg_error_checking_server_version">An error has occurred while checking your server version, please try again</string> <string name="msg_error_checking_server_version">Se ha producido un error al verificar la versión de su servidor, intente de nuevo</string>
<string name="msg_invalid_server_protocol">The selected protocol is not accepted by this server, try using HTTPS</string> <string name="msg_invalid_server_protocol">El protocolo seleccionado no es aceptado por este servidor, intente usar HTTPS</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">Room name changed to: %1$s by %2$s</string> <string name="message_room_name_changed">Nombre de la sala cambiado para: %1$s por %2$s</string>
<string name="message_user_added_by">User %1$s added by %2$s</string> <string name="message_user_added_by">Usuario %1$s añadido por %2$s</string>
<string name="message_user_removed_by">User %1$s removed by %2$s</string> <string name="message_user_removed_by">Usuario %1$s eliminado por %2$s</string>
<string name="message_user_left">Has left the channel.</string> <string name="message_user_left">Ha salido del canal.</string>
<string name="message_user_joined_channel">Has joined the channel.</string> <string name="message_user_joined_channel">Se ha unido al canal.</string>
<string name="message_welcome">Welcome %s</string> <string name="message_welcome">Bienvenido %s</string>
<string name="message_removed">Message removed</string> <string name="message_removed">Mensaje eliminado</string>
<string name="message_pinned">Pinned a message:</string> <string name="message_pinned">Fijado una mensaje:</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Reply</string> <string name="action_msg_reply">Respuesta</string>
<string name="action_msg_edit">Edit</string> <string name="action_msg_edit">Editar</string>
<string name="action_msg_copy">Copy</string> <string name="action_msg_copy">Copiar</string>
<string name="action_msg_quote">Quote</string> <string name="action_msg_quote">Citar</string>
<string name="action_msg_delete">Delete</string> <string name="action_msg_delete">Borrar</string>
<string name="action_msg_pin">Pin Message</string> <string name="action_msg_pin">Fijar mensaje</string>
<string name="action_msg_unpin">Unpin Message</string> <string name="action_msg_unpin">Soltar mensaje</string>
<string name="action_msg_star">Star Message</string> <string name="action_msg_star">Star mensaje</string>
<string name="action_msg_share">Share</string> <string name="action_msg_share">Compartir</string>
<string name="action_title_editing">Editing Message</string> <string name="action_title_editing">Edición de mensaje</string>
<string name="action_msg_add_reaction">Add reaction</string> <string name="action_msg_add_reaction">Añadir una reacción</string>
<!-- Permission messages --> <!-- Permission messages -->
<string name="permission_editing_not_allowed">Editing is not allowed</string> <string name="permission_editing_not_allowed">La edición no és permitida</string>
<string name="permission_deleting_not_allowed">Deleting is not allowed</string> <string name="permission_deleting_not_allowed">Eliminar no és permitido</string>
<string name="permission_pinning_not_allowed">Pinning is not allowed</string> <string name="permission_pinning_not_allowed">Fijar no és permitido</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Members List</string> <string name="title_members_list">Lista de miembros</string>
<!-- Pinned Messages --> <!-- Pinned Messages -->
<string name="title_pinned_messages">Pinned Messages</string> <string name="title_pinned_messages">Mensajes fijados</string>
<string name="no_pinned_messages">No pinned messages</string> <string name="no_pinned_messages">Sin mensajes fijadas</string>
<string name="no_pinned_description">All the pinned messages\nappear here.</string> <string name="no_pinned_description">Todas las mensajes fijadas\naparecen aquí.</string>
<!-- Upload Messages --> <!-- Upload Messages -->
<string name="max_file_size_exceeded">File size %1$d bytes exceeded max upload size of %2$d bytes</string> <string name="max_file_size_exceeded">Tamaño del archivo (%1$d bytes) excedió el tamaño máximo de carga de %2$d bytes</string>
<!-- Socket status --> <!-- Socket status -->
<string name="status_connected">Connected</string> <string name="status_connected">Conectado</string>
<string name="status_disconnected">Disconnected</string> <string name="status_disconnected">Desconectado</string>
<string name="status_connecting">Connecting</string> <string name="status_connecting">Conectando</string>
<string name="status_authenticating">Authenticating</string> <string name="status_authenticating">Autenticando</string>
<string name="status_disconnecting">Disconnecting</string> <string name="status_disconnecting">Desconectando</string>
<string name="status_waiting">Connecting in %d seconds</string> <string name="status_waiting">Conectando en %d segundos</string>
<!--Suggestions--> <!--Suggestions-->
<string name="suggest_all_description">Notify all in this room</string> <string name="suggest_all_description">Notificar a todos en esta sala</string>
<string name="suggest_here_description">Notify active users in this room</string> <string name="suggest_here_description">Notificar usuarios activos en esta sala</string>
<!-- Slash Commands --> <!-- Slash Commands -->
<string name="Slash_Gimme_Description">Displays ༼ つ ◕_◕ ༽つ before your message</string> <string name="Slash_Gimme_Description">Muestra ༼ つ ◕_◕ ༽つ antes de su mensaje</string>
<string name="Slash_LennyFace_Description">Displays ( ͡° ͜ʖ ͡°) after your message</string> <string name="Slash_LennyFace_Description">Mustra ( ͡° ͜ʖ ͡°) después de tu mensaje</string>
<string name="Slash_Shrug_Description">Displays ¯\_(ツ)_/¯ after your message</string> <string name="Slash_Shrug_Description">Muestra ¯\_(ツ)_/¯ después de tu mensaje</string>
<string name="Slash_Tableflip_Description">Displays (╯°□°)╯︵ ┻━┻</string> <string name="Slash_Tableflip_Description">Muestra (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Displays ┬─┬ ノ( ゜-゜ノ)</string> <string name="Slash_TableUnflip_Description">Muestra ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Create a new channel</string> <string name="Create_A_New_Channel">Crea un nuevo canal</string>
<string name="Show_the_keyboard_shortcut_list">Show the keyboard shortcut list</string> <string name="Show_the_keyboard_shortcut_list">Mostrar la lista de atajos de teclado</string>
<string name="Invite_user_to_join_channel_all_from">Invite all users from [#channel] to join this channel</string> <string name="Invite_user_to_join_channel_all_from">Invita a todos los usuarios de [#canal] a unirse a este canal</string>
<string name="Invite_user_to_join_channel_all_to">Invite all users from this channel to join [#channel]</string> <string name="Invite_user_to_join_channel_all_to">Invita a todos los usuarios de este canal a unirse a [#canal]</string>
<string name="Archive">Archive</string> <string name="Archive">Archivo</string>
<string name="Remove_someone_from_room">Remove someone from the room</string> <string name="Remove_someone_from_room">Quita a alguien de la sala</string>
<string name="Leave_the_current_channel">Leave the current channel</string> <string name="Leave_the_current_channel">Deja el canal actual</string>
<string name="Displays_action_text">Displays action text</string> <string name="Displays_action_text">Muestra texto de acción</string>
<string name="Direct_message_someone">Direct message someone</string> <string name="Direct_message_someone">Mensaje directo a alguien</string>
<string name="Mute_someone_in_room">Mute someone in the room</string> <string name="Mute_someone_in_room">Silenciar a alguien en la sala</string>
<string name="Unmute_someone_in_room">Unmute someone in the room</string> <string name="Unmute_someone_in_room">Dejar de silenciar a alguien en la sala</string>
<string name="Invite_user_to_join_channel">Invite one user to join this channel</string> <string name="Invite_user_to_join_channel">Invita a un usuario a unirse a este canal</string>
<string name="Unarchive">Unarchive</string> <string name="Unarchive">Desarchivar</string>
<string name="Join_the_given_channel">Join the given channel</string> <string name="Join_the_given_channel">Únete al canal dado</string>
<string name="Guggy_Command_Description">Generates a gif based upon the provided text</string> <string name="Guggy_Command_Description">Genera un gif basado en el texto proporcionado</string>
<string name="Slash_Topic_Description">Set topic</string> <string name="Slash_Topic_Description">Establecer tema</string>
<!-- Emoji message--> <!-- Emoji message-->
<string name="msg_no_recent_emoji">No recent emoji</string> <string name="msg_no_recent_emoji">Sin emojis recientes</string>
<!-- Sorting and grouping--> <!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Sort</string> <string name="menu_chatroom_sort">Ordenar</string>
<string name="dialog_sort_title">Sort by</string> <string name="dialog_sort_title">Ordenar por</string>
<string name="dialog_sort_by_alphabet">Alphabetical</string> <string name="dialog_sort_by_alphabet">Alfabético</string>
<string name="dialog_sort_by_activity">Activity</string> <string name="dialog_sort_by_activity">Actividad</string>
<string name="dialog_group_by_type">Group by type</string> <string name="dialog_group_by_type">Agrupar por tipo</string>
<string name="dialog_group_favourites">Group favourites</string> <string name="dialog_group_favourites">Agrupar favoritos</string>
<string name="chatroom_header">Header</string> <string name="chatroom_header">Cabezazo</string>
<!--ChatRooms Headers--> <!--ChatRooms Headers-->
<string name="header_channel">Channels</string> <string name="header_channel">Canales</string>
<string name="header_private_groups">Private Groups</string> <string name="header_private_groups">Grupos privados</string>
<string name="header_direct_messages">Direct Messages</string> <string name="header_direct_messages">Mensajes directos</string>
<string name="header_live_chats">Live Chats</string> <string name="header_live_chats">Chats en vivo</string>
<string name="header_unknown">Unknown</string> <string name="header_unknown">Desconocido</string>
<!--Notifications--> <!--Notifications-->
<string name="notif_action_reply_hint">REPLY</string> <string name="notif_action_reply_hint">RESPUESTA</string>
<string name="notif_error_sending">Reply has failed. Please try again.</string> <string name="notif_error_sending">La respuesta ha fallado. Inténtalo de nuevo.</string>
<string name="notif_success_sending">Message sent to %1$s!</string> <string name="notif_success_sending">Mensaje enviado a %1$s!</string>
</resources> </resources>
<resources> <resources>
<!-- Titles --> <!-- Titles -->
<string name="title_sign_in_your_server">Sign in your server</string> <string name="title_sign_in_your_server">Connectez-vous sur votre serveur</string>
<string name="title_log_in">Log in</string> <string name="title_log_in">S\'identifier</string>
<string name="title_register_username">Register username</string> <string name="title_register_username">Enregistrer le nom d\'utilisateur</string>
<string name="title_sign_up">Sign up</string> <string name="title_sign_up">S\'inscrire</string>
<string name="title_authentication">Authentication</string> <string name="title_authentication">Authentification</string>
<string name="title_legal_terms">Legal Terms</string> <string name="title_legal_terms">Termes légaux</string>
<string name="title_chats">Chats</string> <string name="title_chats">Chats</string>
<string name="title_profile">Profile</string> <string name="title_profile">Profil</string>
<string name="title_members">Members (%d)</string> <string name="title_members">Membres (%d)</string>
<string name="title_settings">Settings</string> <string name="title_settings">Paramètres</string>
<string name="title_password">Change Password</string> <string name="title_password">Changer le mot de passe</string>
<string name="title_update_profile">Update profile</string> <string name="title_update_profile">Update profile</string>
<string name="title_about">About</string> <string name="title_about">Sur</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Connect</string> <string name="action_connect">Se connecter</string>
<string name="action_use_this_username">Use this username</string> <string name="action_use_this_username">Utilisez ce nom d\'utilisateur</string>
<string name="action_login_or_sign_up">Tap this button to log in or create an account</string> <string name="action_login_or_sign_up">Touchez ce bouton pour vous connecter ou créer un compte</string>
<string name="action_terms_of_service">Terms of Service</string> <string name="action_terms_of_service">Conditions d\'utilisation</string>
<string name="action_privacy_policy">Privacy Policy</string> <string name="action_privacy_policy">Politique de confidentialité</string>
<string name="action_search">Search</string> <string name="action_search">Chercher</string>
<string name="action_update">Update</string> <string name="action_update">Mettre à jour</string>
<string name="action_settings">Settings</string> <string name="action_settings">Paramètres</string>
<string name="action_logout">Logout</string> <string name="action_logout">Se déconnecter</string>
<string name="action_files">Files</string> <string name="action_files">Fichiers</string>
<string name="action_confirm_password">Confirm Password Change</string> <string name="action_confirm_password">Confirmer le mot de passe</string>
<string name="action_join_chat">Join Chat</string> <string name="action_join_chat">Rejoignez le chat</string>
<string name="action_add_account">Add account</string> <string name="action_add_account">Ajouter un compte</string>
<string name="action_online">Online</string> <string name="action_online">En ligne</string>
<string name="action_away">Away</string> <string name="action_away">Loin</string>
<string name="action_busy">Busy</string> <string name="action_busy">Occupé</string>
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_password">Change Password</item> <item name="item_password">Changer le mot de passe</item>
<item name="item_password">About</item> <item name="item_password">Sur</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
<string name="msg_generic_error">Sorry, an error has occurred, please try again</string> <string name="msg_generic_error">Désolé, une erreur s\'est produite. Veuillez réessayer</string>
<string name="msg_no_data_to_display">No data to display</string> <string name="msg_no_data_to_display">Aucune donnée à afficher</string>
<string name="msg_profile_update_successfully">Profile update successfully</string> <string name="msg_profile_update_successfully">Mise à jour du profil avec succès</string>
<string name="msg_username">username</string> <string name="msg_username">nom d\'utilisateur</string>
<string name="msg_username_or_email">username or email</string> <string name="msg_username_or_email">nom d\'utilisateur ou email</string>
<string name="msg_password">password</string> <string name="msg_password">mot de passe</string>
<string name="msg_name">name</string> <string name="msg_name">prénom</string>
<string name="msg_email">email</string> <string name="msg_email">email</string>
<string name="msg_avatar_url">avatar URL</string> <string name="msg_avatar_url">URL de l\'avatar</string>
<string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string> <string name="msg_or_continue_using_social_accounts">Ou continuer en utilisant les comptes sociaux</string>
<string name="msg_new_user">New user? %1$s</string> <string name="msg_new_user">Nouvel utilisateur? %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">En procédant, vous acceptez notre\n%1$s et %2$s</string>
<string name="msg_2fa_code">2FA Code</string> <string name="msg_2fa_code">Code 2FA</string>
<string name="msg_yesterday">Yesterday</string> <string name="msg_yesterday">Hier</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">Cette salle est seulement de lecture</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string> <string name="msg_invalid_2fa_code">Code 2FA non valide</string>
<string name="msg_invalid_file">Invalid file</string> <string name="msg_invalid_file">Fichier non valide</string>
<string name="msg_invalid_server_url">Invalid server URL</string> <string name="msg_invalid_server_url">URL de serveur non valide</string>
<string name="msg_content_description_log_in_using_facebook">Login using Facebook</string> <string name="msg_content_description_log_in_using_facebook">Connectez-vous en utilisant Facebook</string>
<string name="msg_content_description_log_in_using_github">Login using Github</string> <string name="msg_content_description_log_in_using_github">Connectez-vous en utilisant Github</string>
<string name="msg_content_description_log_in_using_google">Login using Google</string> <string name="msg_content_description_log_in_using_google">Connectez-vous en utilisant Google</string>
<string name="msg_content_description_log_in_using_linkedin">Login using Linkedin</string> <string name="msg_content_description_log_in_using_linkedin">Connectez-vous en utilisant Linkedin</string>
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string> <string name="msg_content_description_log_in_using_meteor">Connectez-vous en utilisant Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string> <string name="msg_content_description_log_in_using_twitter">Connectez-vous en utilisant Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Connectez-vous en utilisant Gitlab</string>
<string name="msg_content_description_send_message">Send message</string> <string name="msg_content_description_send_message">Envoyer message</string>
<string name="msg_content_description_show_attachment_options">Show attachment options</string> <string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string>
<string name="msg_you">You</string> <string name="msg_you">Toi</string>
<string name="msg_unknown">Unknown</string> <string name="msg_unknown">Inconnu</string>
<string name="msg_email_address">E-mail address</string> <string name="msg_email_address">Adresse e-mail</string>
<string name="msg_utc_offset">UTC offset</string> <string name="msg_utc_offset">Décalage UTC</string>
<string name="msg_new_password">Enter New Password</string> <string name="msg_new_password">Entrez un nouveau mot de passe</string>
<string name="msg_confirm_password">Confirm New Password</string> <string name="msg_confirm_password">Confirmer le nouveau mot de passe</string>
<string name="msg_unread_messages">Unread messages</string> <string name="msg_unread_messages">Messages non lus</string>
<string name="msg_preview_video">Video</string> <string name="msg_preview_video">Vidéo</string>
<string name="msg_preview_audio">Audio</string> <string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Photo</string> <string name="msg_preview_photo">Photo</string>
<string name="msg_preview_file">File</string> <string name="msg_preview_file">File</string>
<string name="msg_no_messages_yet">No messages yet</string> <string name="msg_no_messages_yet">Aucun message pour le moment</string>
<string name="msg_version">Version %1$s</string> <string name="msg_version">Version %1$s</string>
<string name="msg_build">Build %1$d</string> <string name="msg_build">Build %1$d</string>
<string name="msg_ok">OK</string> <string name="msg_ok">OK</string>
<string name="msg_ver_not_recommended"> <string name="msg_ver_not_recommended">
Looks like your server version is below the recommended version %1$s.\nYou can still login but you may experience unexpected behaviors.</string> On dirait que la version de votre serveur est en dessous de la version recommandée %1$s.\nVous pouvez toujours vous connecter mais vous pouvez rencontrer des comportements inattendus.</string>
<string name="msg_ver_not_minimum"> <string name="msg_ver_not_minimum">
Looks like your server version is below the minimum required version %1$s.\nPlease upgrade your server to login! On dirait que la version de votre serveur est inférieure à la version minimale requise %1$s.\nVeuillez mettre à jour votre serveur pour vous connecter!
</string> </string>
<string name="msg_proceed">PROCEED</string> <string name="msg_proceed">PROCÉDER</string>
<string name="msg_cancel">CANCEL</string> <string name="msg_cancel">ANNULER</string>
<string name="msg_warning">WARNING</string> <string name="msg_warning">ATTENTION</string>
<string name="msg_http_insecure">When using HTTP, you\'re connecting to an insecure server. We don\'t recommend you doing that.</string> <string name="msg_http_insecure">Lorsque vous utilisez HTTP, vous vous connectez à un serveur non sécurisé. Nous ne vous recommandons pas de le faire.</string>
<string name="msg_error_checking_server_version">An error has occurred while checking your server version, please try again</string> <string name="msg_error_checking_server_version">Une erreur est survenue lors de la vérification de la version de votre serveur, veuillez réessayer</string>
<string name="msg_invalid_server_protocol">The selected protocol is not accepted by this server, try using HTTPS</string> <string name="msg_invalid_server_protocol">Le protocole sélectionné n\'est pas accepté par ce serveur, essayez d\'utiliser HTTPS</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">Room name changed to: %1$s by %2$s</string> <string name="message_room_name_changed">Le nom de le salle a changé à: %1$s par %2$s</string>
<string name="message_user_added_by">User %1$s added by %2$s</string> <string name="message_user_added_by">Utilisateur %1$s ajouté par %2$s</string>
<string name="message_user_removed_by">User %1$s removed by %2$s</string> <string name="message_user_removed_by">Utilisateur %1$s enlevé par %2$s</string>
<string name="message_user_left">Has left the channel.</string> <string name="message_user_left">A quitté de la salle.</string>
<string name="message_user_joined_channel">Has joined the channel.</string> <string name="message_user_joined_channel">A rejoint la salle.</string>
<string name="message_welcome">Welcome %s</string> <string name="message_welcome">Bienvenue %s</string>
<string name="message_removed">Message removed</string> <string name="message_removed">Message supprimé</string>
<string name="message_pinned">Pinned a message:</string> <string name="message_pinned">Épinglé un message:</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Reply</string> <string name="action_msg_reply">Répondre</string>
<string name="action_msg_edit">Edit</string> <string name="action_msg_edit">Modifier</string>
<string name="action_msg_copy">Copy</string> <string name="action_msg_copy">Copier</string>
<string name="action_msg_quote">Quote</string> <string name="action_msg_quote">Citation</string>
<string name="action_msg_delete">Delete</string> <string name="action_msg_delete">Effacer</string>
<string name="action_msg_pin">Pin Message</string> <string name="action_msg_pin">Épingle message</string>
<string name="action_msg_unpin">Unpin Message</string> <string name="action_msg_unpin">Enlever message</string>
<string name="action_msg_star">Star Message</string> <string name="action_msg_star">Star message</string>
<string name="action_msg_share">Share</string> <string name="action_msg_share">Partager</string>
<string name="action_title_editing">Editing Message</string> <string name="action_title_editing">Modification du message</string>
<string name="action_msg_add_reaction">Add reaction</string> <string name="action_msg_add_reaction">Ajouter une réaction</string>
<!-- Permission messages --> <!-- Permission messages -->
<string name="permission_editing_not_allowed">Editing is not allowed</string> <string name="permission_editing_not_allowed">L\'édition n\'est pas autorisée</string>
<string name="permission_deleting_not_allowed">Deleting is not allowed</string> <string name="permission_deleting_not_allowed">La suppression n\'est pas autorisée</string>
<string name="permission_pinning_not_allowed">Pinning is not allowed</string> <string name="permission_pinning_not_allowed">L\'épinglage n\'est pas autorisé</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Members List</string> <string name="title_members_list">Liste des membres</string>
<!-- Pinned Messages --> <!-- Pinned Messages -->
<string name="title_pinned_messages">Pinned Messages</string> <string name="title_pinned_messages">Messages épinglés</string>
<string name="no_pinned_messages">No pinned messages</string> <string name="no_pinned_messages">Aucun message épinglé</string>
<string name="no_pinned_description">All the pinned messages\nappear here.</string> <string name="no_pinned_description">Tous les messages épinglés\napparaissent ici.</string>
<!-- Upload Messages --> <!-- Upload Messages -->
<string name="max_file_size_exceeded">File size %1$d bytes exceeded max upload size of %2$d bytes</string> <string name="max_file_size_exceeded">Taille du fichier (%1$d bytes) dépassé la taille de téléchargement maximale de %2$d bytes</string>
<!-- Socket status --> <!-- Socket status -->
<string name="status_connected">Connected</string> <string name="status_connected">Connecté</string>
<string name="status_disconnected">Disconnected</string> <string name="status_disconnected">Détaché</string>
<string name="status_connecting">Connecting</string> <string name="status_connecting">Connexion</string>
<string name="status_authenticating">Authenticating</string> <string name="status_authenticating">Authentification</string>
<string name="status_disconnecting">Disconnecting</string> <string name="status_disconnecting">Déconnexion</string>
<string name="status_waiting">Connecting in %d seconds</string> <string name="status_waiting">Connexion en %d secondes</string>
<!--Suggestions--> <!--Suggestions-->
<string name="suggest_all_description">Notify all in this room</string> <string name="suggest_all_description">Notifier tout dans cette salle</string>
<string name="suggest_here_description">Notify active users in this room</string> <string name="suggest_here_description">Notifier les utilisateurs actifs dans cette salle</string>
<!-- Slash Commands --> <!-- Slash Commands -->
<string name="Slash_Gimme_Description">Displays ༼ つ ◕_◕ ༽つ before your message</string> <string name="Slash_Gimme_Description">Affiche ༼ つ ◕_◕ ༽つ avant votre message</string>
<string name="Slash_LennyFace_Description">Displays ( ͡° ͜ʖ ͡°) after your message</string> <string name="Slash_LennyFace_Description">Affiche ( ͡° ͜ʖ ͡°) après votre message</string>
<string name="Slash_Shrug_Description">Displays ¯\_(ツ)_/¯ after your message</string> <string name="Slash_Shrug_Description">Affiche ¯\_(ツ)_/¯ après votre message</string>
<string name="Slash_Tableflip_Description">Displays (╯°□°)╯︵ ┻━┻</string> <string name="Slash_Tableflip_Description">Affiche (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Displays ┬─┬ ノ( ゜-゜ノ)</string> <string name="Slash_TableUnflip_Description">Affiche ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Create a new channel</string> <string name="Create_A_New_Channel">Créer une nouvelle salle</string>
<string name="Show_the_keyboard_shortcut_list">Show the keyboard shortcut list</string> <string name="Show_the_keyboard_shortcut_list">Afficher la liste des raccourcis clavier</string>
<string name="Invite_user_to_join_channel_all_from">Invite all users from [#channel] to join this channel</string> <string name="Invite_user_to_join_channel_all_from">Inviter tous les utilisateurs de [#salle] à rejoindre cette salle</string>
<string name="Invite_user_to_join_channel_all_to">Invite all users from this channel to join [#channel]</string> <string name="Invite_user_to_join_channel_all_to">Inviter tous les utilisateurs de cette salle à rejoindre [#salle]</string>
<string name="Archive">Archive</string> <string name="Archive">Archiver</string>
<string name="Remove_someone_from_room">Remove someone from the room</string> <string name="Remove_someone_from_room">Retirer quelqu\'un de la salle</string>
<string name="Leave_the_current_channel">Leave the current channel</string> <string name="Leave_the_current_channel">Sortir de la salle actuelle</string>
<string name="Displays_action_text">Displays action text</string> <string name="Displays_action_text">Affiche le texte d\'action</string>
<string name="Direct_message_someone">Direct message someone</string> <string name="Direct_message_someone">Message direct avec quelqu\'un</string>
<string name="Mute_someone_in_room">Mute someone in the room</string> <string name="Mute_someone_in_room">Mettre en sourdine une personne dans la salle</string>
<string name="Unmute_someone_in_room">Unmute someone in the room</string> <string name="Unmute_someone_in_room">Retirer la sourdine d\'une personne dans la salle</string>
<string name="Invite_user_to_join_channel">Invite one user to join this channel</string> <string name="Invite_user_to_join_channel">Inviter un utilisateur à rejoindre cette salle</string>
<string name="Unarchive">Unarchive</string> <string name="Unarchive">Désarchiver</string>
<string name="Join_the_given_channel">Join the given channel</string> <string name="Join_the_given_channel">Rejoignez la salle fourni</string>
<string name="Guggy_Command_Description">Generates a gif based upon the provided text</string> <string name="Guggy_Command_Description">Génère un gif basé sur le texte fourni</string>
<string name="Slash_Topic_Description">Set topic</string> <string name="Slash_Topic_Description">Définir le sujet</string>
<!-- Emoji message--> <!-- Emoji message-->
<string name="msg_no_recent_emoji">No recent emoji</string> <string name="msg_no_recent_emoji">Aucun emoji récent</string>
<!-- Sorting and grouping--> <!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Sort</string> <string name="menu_chatroom_sort">Trier</string>
<string name="dialog_sort_title">Sort by</string> <string name="dialog_sort_title">Trier par</string>
<string name="dialog_sort_by_alphabet">Alphabetical</string> <string name="dialog_sort_by_alphabet">Alphabétique</string>
<string name="dialog_sort_by_activity">Activity</string> <string name="dialog_sort_by_activity">Activité</string>
<string name="dialog_group_by_type">Group by type</string> <string name="dialog_group_by_type">Grouper par type</string>
<string name="dialog_group_favourites">Group favourites</string> <string name="dialog_group_favourites">Grouper favoris</string>
<string name="chatroom_header">Header</string> <string name="chatroom_header">Entête</string>
<!--ChatRooms Headers--> <!--ChatRooms Headers-->
<string name="header_channel">Channels</string> <string name="header_channel">Salles</string>
<string name="header_private_groups">Private Groups</string> <string name="header_private_groups">Groupes privés</string>
<string name="header_direct_messages">Direct Messages</string> <string name="header_direct_messages">Messages directs</string>
<string name="header_live_chats">Live Chats</string> <string name="header_live_chats">Chats en direct</string>
<string name="header_unknown">Unknown</string> <string name="header_unknown">Inconnu</string>
<!--Notifications--> <!--Notifications-->
<string name="notif_action_reply_hint">REPLY</string> <string name="notif_action_reply_hint">RÉPONDRE</string>
<string name="notif_error_sending">Reply has failed. Please try again.</string> <string name="notif_error_sending">La réponse a échoué. Veuillez réessayer.</string>
<string name="notif_success_sending">Message sent to %1$s!</string> <string name="notif_success_sending">Message envoyé à %1$s!</string>
</resources> </resources>
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Conectar</string> <string name="action_connect">Conectar</string>
<string name="action_use_this_username">Usar este nome de usuário</string> <string name="action_use_this_username">Usar este nome de usuário</string>
<string name="action_login_or_sign_up">Toque este botão para fazer login ou criar uma conta</string> <string name="action_login_or_sign_up">Toque neste botão para fazer login ou criar uma conta</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>
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
<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_2fa_code">Código 2FA inválido</string>
<string name="msg_invalid_file">Arquivo 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_invalid_server_url">URL de servidor inválido</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>
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
<string name="message_user_added_by">Usuário %1$s adicionado por %2$s</string> <string name="message_user_added_by">Usuário %1$s adicionado por %2$s</string>
<string name="message_user_removed_by">Usuário %1$s removido por %2$s</string> <string name="message_user_removed_by">Usuário %1$s removido por %2$s</string>
<string name="message_user_left">Saiu da sala.</string> <string name="message_user_left">Saiu da sala.</string>
<string name="message_user_joined_channel">Entrou no sala.</string> <string name="message_user_joined_channel">Entrou na sala.</string>
<string name="message_welcome">Bem-vindo, %s</string> <string name="message_welcome">Bem-vindo, %s</string>
<string name="message_removed">Mensagem removida</string> <string name="message_removed">Mensagem removida</string>
<string name="message_pinned">Pinou uma mensagem:</string> <string name="message_pinned">Pinou uma mensagem:</string>
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
<!-- Permission messages --> <!-- Permission messages -->
<string name="permission_editing_not_allowed">Edição não permitida</string> <string name="permission_editing_not_allowed">Edição não permitida</string>
<string name="permission_deleting_not_allowed">Remoção não permitida</string> <string name="permission_deleting_not_allowed">Remoção não permitida</string>
<string name="permission_pinning_not_allowed">Fixar não permitido</string> <string name="permission_pinning_not_allowed">Pinagem não permitida</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Lista de Membros</string> <string name="title_members_list">Lista de Membros</string>
...@@ -163,8 +163,8 @@ ...@@ -163,8 +163,8 @@
<string name="Leave_the_current_channel">Sair do canal atual</string> <string name="Leave_the_current_channel">Sair do canal atual</string>
<string name="Displays_action_text">Exibir texto de ação</string> <string name="Displays_action_text">Exibir texto de ação</string>
<string name="Direct_message_someone">Enviar DM para alguém</string> <string name="Direct_message_someone">Enviar DM para alguém</string>
<string name="Mute_someone_in_room">Mutar alguém</string> <string name="Mute_someone_in_room">Silenciar alguém</string>
<string name="Unmute_someone_in_room">Desmutar alguém na sala</string> <string name="Unmute_someone_in_room">De-silenciar alguém na sala</string>
<string name="Invite_user_to_join_channel">Convidar algum usuário para entrar neste canal</string> <string name="Invite_user_to_join_channel">Convidar algum usuário para entrar neste canal</string>
<string name="Unarchive">Desarquivar</string> <string name="Unarchive">Desarquivar</string>
<string name="Join_the_given_channel">Entrar no canal especificado</string> <string name="Join_the_given_channel">Entrar no canal especificado</string>
......
...@@ -10,7 +10,7 @@ buildscript { ...@@ -10,7 +10,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.1' classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.2.0' classpath 'com.google.gms:google-services:3.2.0'
......
...@@ -4,7 +4,7 @@ ext { ...@@ -4,7 +4,7 @@ ext {
compileSdk : 27, compileSdk : 27,
targetSdk : 27, targetSdk : 27,
buildTools : '27.0.3', buildTools : '27.0.3',
kotlin : '1.2.31', kotlin : '1.2.40',
coroutine : '0.22.5', coroutine : '0.22.5',
dokka : '0.9.16', dokka : '0.9.16',
...@@ -91,7 +91,6 @@ ext { ...@@ -91,7 +91,6 @@ ext {
frescoImageViewer : "com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}", frescoImageViewer : "com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}",
markwon : "ru.noties:markwon:${versions.markwon}", markwon : "ru.noties:markwon:${versions.markwon}",
markwonImageLoader : "ru.noties:markwon-image-loader:${versions.markwon}",
sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}", sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}",
......
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