Unverified Commit 0c11cb56 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #1552 from RocketChat/beta

[RELEASE] merge 2.5.1 to master
parents 68a1626c be97e8ff
...@@ -12,8 +12,8 @@ android { ...@@ -12,8 +12,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2034 versionCode 2035
versionName "2.5.0" versionName "2.5.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
......
...@@ -11,8 +11,8 @@ import timber.log.Timber ...@@ -11,8 +11,8 @@ import timber.log.Timber
@Parcelize @Parcelize
data class LoginDeepLinkInfo( data class LoginDeepLinkInfo(
val url: String, val url: String,
val userId: String, val userId: String?,
val token: String val token: String?
) : Parcelable ) : Parcelable
fun Intent.getLoginDeepLinkInfo(): LoginDeepLinkInfo? { fun Intent.getLoginDeepLinkInfo(): LoginDeepLinkInfo? {
......
...@@ -23,10 +23,10 @@ import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled ...@@ -23,10 +23,10 @@ import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled
import chat.rocket.android.server.domain.isLdapAuthenticationEnabled import chat.rocket.android.server.domain.isLdapAuthenticationEnabled
import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled
import chat.rocket.android.server.domain.isLoginFormEnabled import chat.rocket.android.server.domain.isLoginFormEnabled
import chat.rocket.android.server.domain.isMeteorAuthenticationEnabled import chat.rocket.android.server.domain.isWordpressAuthenticationEnabled
import chat.rocket.android.server.domain.isPasswordResetEnabled import chat.rocket.android.server.domain.isPasswordResetEnabled
import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers
import chat.rocket.android.server.domain.isTwitterAuthenticationEnabled import chat.rocket.android.server.domain.wordpressUrl
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
...@@ -72,6 +72,7 @@ private const val SERVICE_NAME_GITHUB = "github" ...@@ -72,6 +72,7 @@ private const val SERVICE_NAME_GITHUB = "github"
private const val SERVICE_NAME_GOOGLE = "google" private const val SERVICE_NAME_GOOGLE = "google"
private const val SERVICE_NAME_LINKEDIN = "linkedin" private const val SERVICE_NAME_LINKEDIN = "linkedin"
private const val SERVICE_NAME_GILAB = "gitlab" private const val SERVICE_NAME_GILAB = "gitlab"
private const val SERVICE_NAME_WORDPRESS = "wordpress"
class LoginPresenter @Inject constructor( class LoginPresenter @Inject constructor(
private val view: LoginView, private val view: LoginView,
...@@ -141,10 +142,15 @@ class LoginPresenter @Inject constructor( ...@@ -141,10 +142,15 @@ class LoginPresenter @Inject constructor(
fun authenticateWithDeepLink(deepLinkInfo: LoginDeepLinkInfo) { fun authenticateWithDeepLink(deepLinkInfo: LoginDeepLinkInfo) {
val serverUrl = deepLinkInfo.url val serverUrl = deepLinkInfo.url
setupConnectionInfo(serverUrl) setupConnectionInfo(serverUrl)
if (deepLinkInfo.userId != null && deepLinkInfo.token != null) {
deepLinkUserId = deepLinkInfo.userId deepLinkUserId = deepLinkInfo.userId
deepLinkToken = deepLinkInfo.token deepLinkToken = deepLinkInfo.token
tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken)) tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken))
doAuthentication(TYPE_LOGIN_DEEP_LINK) doAuthentication(TYPE_LOGIN_DEEP_LINK)
} else {
// If we don't have the login credentials, just go through normal setup and user input.
setupView()
}
} }
private fun setupConnectionInfo(serverUrl: String) { private fun setupConnectionInfo(serverUrl: String) {
...@@ -204,8 +210,8 @@ class LoginPresenter @Inject constructor( ...@@ -204,8 +210,8 @@ class LoginPresenter @Inject constructor(
var totalSocialAccountsEnabled = 0 var totalSocialAccountsEnabled = 0
if (settings.isFacebookAuthenticationEnabled()) { if (settings.isFacebookAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_FACEBOOK) getServiceMap(services, SERVICE_NAME_FACEBOOK)?.let { serviceMap ->
if (clientId != null) { getOauthClientId(serviceMap)?.let { clientId ->
view.setupFacebookButtonListener( view.setupFacebookButtonListener(
OauthHelper.getFacebookOauthUrl( OauthHelper.getFacebookOauthUrl(
clientId, clientId,
...@@ -217,9 +223,11 @@ class LoginPresenter @Inject constructor( ...@@ -217,9 +223,11 @@ class LoginPresenter @Inject constructor(
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
}
if (settings.isGithubAuthenticationEnabled()) { if (settings.isGithubAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GITHUB) getServiceMap(services, SERVICE_NAME_GITHUB)?.let { serviceMap ->
if (clientId != null) { getOauthClientId(serviceMap)?.let { clientId ->
view.setupGithubButtonListener( view.setupGithubButtonListener(
OauthHelper.getGithubOauthUrl( OauthHelper.getGithubOauthUrl(
clientId, clientId,
...@@ -230,9 +238,11 @@ class LoginPresenter @Inject constructor( ...@@ -230,9 +238,11 @@ class LoginPresenter @Inject constructor(
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
}
if (settings.isGoogleAuthenticationEnabled()) { if (settings.isGoogleAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GOOGLE) getServiceMap(services, SERVICE_NAME_GOOGLE)?.let { serviceMap ->
if (clientId != null) { getOauthClientId(serviceMap)?.let { clientId ->
view.setupGoogleButtonListener( view.setupGoogleButtonListener(
OauthHelper.getGoogleOauthUrl( OauthHelper.getGoogleOauthUrl(
clientId, clientId,
...@@ -244,9 +254,11 @@ class LoginPresenter @Inject constructor( ...@@ -244,9 +254,11 @@ class LoginPresenter @Inject constructor(
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
}
if (settings.isLinkedinAuthenticationEnabled()) { if (settings.isLinkedinAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_LINKEDIN) getServiceMap(services, SERVICE_NAME_LINKEDIN)?.let { serviceMap ->
if (clientId != null) { getOauthClientId(serviceMap)?.let { clientId ->
view.setupLinkedinButtonListener( view.setupLinkedinButtonListener(
OauthHelper.getLinkedinOauthUrl( OauthHelper.getLinkedinOauthUrl(
clientId, clientId,
...@@ -256,21 +268,14 @@ class LoginPresenter @Inject constructor( ...@@ -256,21 +268,14 @@ class LoginPresenter @Inject constructor(
) )
view.enableLoginByLinkedin() view.enableLoginByLinkedin()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
if (settings.isMeteorAuthenticationEnabled()) {
//TODO: Remove until we have this implemented
// view.enableLoginByMeteor()
// totalSocialAccountsEnabled++
}
if (settings.isTwitterAuthenticationEnabled()) {
//TODO: Remove until Twitter provides support to OAuth2
// view.enableLoginByTwitter()
// totalSocialAccountsEnabled++
} }
if (settings.isGitlabAuthenticationEnabled()) { if (settings.isGitlabAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GILAB) getServiceMap(services, SERVICE_NAME_GILAB)?.let { serviceMap ->
if (clientId != null) { getOauthClientId(serviceMap)?.let { clientId ->
val gitlabOauthUrl = if (settings.gitlabUrl() != null) { val gitlabOauthUrl = if (settings.gitlabUrl() != null) {
OauthHelper.getGitlabOauthUrl( OauthHelper.getGitlabOauthUrl(
host = settings.gitlabUrl(), host = settings.gitlabUrl(),
...@@ -290,46 +295,105 @@ class LoginPresenter @Inject constructor( ...@@ -290,46 +295,105 @@ class LoginPresenter @Inject constructor(
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
}
getCustomOauthServices(services).let { if (settings.isWordpressAuthenticationEnabled()) {
for (service in it) { getServiceMap(services, SERVICE_NAME_WORDPRESS)?.let { serviceMap ->
val serviceName = getCustomOauthServiceName(service) getOauthClientId(serviceMap)?.let { clientId ->
val wordpressOauthUrl =
if (settings.wordpressUrl().isNullOrEmpty()) {
OauthHelper.getWordpressComOauthUrl(
clientId,
currentServer,
state
)
} else {
OauthHelper.getWordpressCustomOauthUrl(
getCustomOauthHost(serviceMap)
?: "https://public-api.wordpress.com",
getCustomOauthAuthorizePath(serviceMap)
?: "/oauth/authorize",
clientId,
currentServer,
SERVICE_NAME_WORDPRESS,
state,
getCustomOauthScope(serviceMap) ?: "openid"
)
}
wordpressOauthUrl?.let {
view.setupWordpressButtonListener(it, state)
view.enableLoginByWordpress()
totalSocialAccountsEnabled++
}
}
}
}
getCustomOauthServices(services).let {
for (serviceMap in it) {
val serviceName = getCustomOauthServiceName(serviceMap)
val host = getCustomOauthHost(serviceMap)
val authorizePath = getCustomOauthAuthorizePath(serviceMap)
val clientId = getOauthClientId(serviceMap)
val scope = getCustomOauthScope(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (serviceName != null &&
host != null &&
authorizePath != null &&
clientId != null &&
scope != null &&
textColor != null &&
buttonColor != null
) {
val customOauthUrl = OauthHelper.getCustomOauthUrl( val customOauthUrl = OauthHelper.getCustomOauthUrl(
getCustomOauthHost(service), host,
getCustomOauthAuthorizePath(service), authorizePath,
getCustomOauthClientId(service), clientId,
currentServer, currentServer,
serviceName, serviceName,
state, state,
getCustomOauthScope(service) scope
) )
view.addCustomOauthServiceButton( view.addCustomOauthServiceButton(
customOauthUrl, customOauthUrl,
state, state,
serviceName, serviceName,
getServiceNameColor(service), textColor,
getServiceButtonColor(service) buttonColor
) )
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
}
getSamlServices(services).let { getSamlServices(services).let {
val samlToken = generateRandomString(17) val samlToken = generateRandomString(17)
for (serviceMap in it) {
for (service in it) { val provider = getSamlProvider(serviceMap)
val serviceName = getSamlServiceName(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (provider != null &&
serviceName != null &&
textColor != null &&
buttonColor != null
) {
view.addSamlServiceButton( view.addSamlServiceButton(
currentServer.samlUrl(getSamlProvider(service), samlToken), currentServer.samlUrl(provider, samlToken),
samlToken, samlToken,
getSamlServiceName(service), serviceName,
getServiceNameColor(service), textColor,
getServiceButtonColor(service) buttonColor
) )
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
}
if (totalSocialAccountsEnabled > 0) { if (totalSocialAccountsEnabled > 0) {
view.enableOauthView() view.enableOauthView()
...@@ -433,55 +497,114 @@ class LoginPresenter @Inject constructor( ...@@ -433,55 +497,114 @@ class LoginPresenter @Inject constructor(
} }
} }
private fun getOauthClientId(listMap: List<Map<String, Any>>, serviceName: String): String? { /**
return listMap.find { map -> map.containsValue(serviceName) }?.let { * Returns an OAuth service map given a [serviceName].
it["clientId"] ?: it["appId"] *
}.toString() * @param listMap The list of [Map] to get the service from.
} * @param serviceName The service name to get in the [listMap]
* @return The OAuth service map or null otherwise.
private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> { */
return listMap.filter { map -> map["service"] == "saml" } private fun getServiceMap(
} listMap: List<Map<String, Any>>,
serviceName: String
private fun getSamlServiceName(service: Map<String, Any>): String { ): Map<String, Any>? = listMap.find { map -> map.containsValue(serviceName) }
return service["buttonLabelText"].toString()
} /**
* Returns the OAuth client ID of a [serviceMap].
private fun getSamlProvider(service: Map<String, Any>): String { * REMARK: This function works for common OAuth providers (Google, Facebook, Github and so on)
return (service["clientConfig"] as Map<*, *>)["provider"].toString() * as well as custom OAuth.
} *
* @param serviceMap The service map to get the OAuth client ID.
private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> { * @return The OAuth client ID or null otherwise.
return listMap.filter { map -> map["custom"] == true } */
} private fun getOauthClientId(serviceMap: Map<String, Any>): String? =
serviceMap["clientId"] as? String ?: serviceMap["appId"] as? String
private fun getCustomOauthHost(service: Map<String, Any>): String {
return service["serverURL"].toString() /**
} * Returns a custom OAuth service list.
*
private fun getCustomOauthAuthorizePath(service: Map<String, Any>): String { * @return A custom OAuth service list, otherwise an empty list if there is no custom OAuth service.
return service["authorizePath"].toString() */
} private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["custom"] == true }
private fun getCustomOauthClientId(service: Map<String, Any>): String {
return service["clientId"].toString() /** Returns the custom OAuth service host.
} *
* @param serviceMap The service map to get the custom OAuth service host.
private fun getCustomOauthServiceName(service: Map<String, Any>): String { * @return The custom OAuth service host, otherwise null.
return service["service"].toString() */
} private fun getCustomOauthHost(serviceMap: Map<String, Any>): String? =
serviceMap["serverURL"] as? String
private fun getCustomOauthScope(service: Map<String, Any>): String {
return service["scope"].toString() /** Returns the custom OAuth service authorize path.
} *
* @param serviceMap The service map to get the custom OAuth service authorize path.
private fun getServiceButtonColor(service: Map<String, Any>): Int { * @return The custom OAuth service authorize path, otherwise null.
return service["buttonColor"].toString().parseColor() */
} private fun getCustomOauthAuthorizePath(serviceMap: Map<String, Any>): String? =
serviceMap["authorizePath"] as? String
private fun getServiceNameColor(service: Map<String, Any>): Int {
return service["buttonLabelColor"].toString().parseColor() /** Returns the custom OAuth service scope.
} *
* @param serviceMap The service map to get the custom OAuth service scope.
* @return The custom OAuth service scope, otherwise null.
*/
private fun getCustomOauthScope(serviceMap: Map<String, Any>): String? =
serviceMap["scope"] as? String
/** Returns the text of the custom OAuth service.
*
* @param serviceMap The service map to get the text of the custom OAuth service.
* @return The text of the custom OAuth service, otherwise null.
*/
private fun getCustomOauthServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["service"] as? String
/**
* Returns a SAML OAuth service list.
*
* @return A SAML service list, otherwise an empty list if there is no SAML OAuth service.
*/
private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["service"] == "saml" }
/**
* Returns the SAML provider.
*
* @param serviceMap The service map to provider from.
* @return The SAML provider, otherwise null.
*/
private fun getSamlProvider(serviceMap: Map<String, Any>): String? =
(serviceMap["clientConfig"] as Map<*, *>)["provider"] as? String
/**
* Returns the text of the SAML service.
*
* @param serviceMap The service map to get the text of the SAML service.
* @return The text of the SAML service, otherwise null.
*/
private fun getSamlServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["buttonLabelText"] as? String
/**
* Returns the text color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the text color from.
* @return The text color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceNameColorForCustomOauthOrSaml(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonLabelColor"] as? String)?.parseColor()
/**
* Returns the button color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the button color from.
* @return The button color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceButtonColor(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonColor"] as? String)?.parseColor()
private suspend fun saveAccount(username: String) { private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
......
...@@ -190,6 +190,21 @@ interface LoginView : LoadingView, MessageView { ...@@ -190,6 +190,21 @@ interface LoginView : LoadingView, MessageView {
*/ */
fun setupGitlabButtonListener(gitlabUrl: String, state: String) fun setupGitlabButtonListener(gitlabUrl: String, state: String)
/**
* Shows the "login by WordPress" view if it is enable by the server settings.
*
* REMARK: We must set up the Gitlab button listener before enabling it [setupWordpressButtonListener].
*/
fun enableLoginByWordpress()
/**
* Setups the WordPress button when tapped.
*
* @param wordpressUrl The WordPress OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/
fun setupWordpressButtonListener(wordpressUrl: String, state: String)
/** /**
* Adds a custom OAuth button in the oauth view. * Adds a custom OAuth button in the oauth view.
* *
......
...@@ -443,6 +443,24 @@ class LoginFragment : Fragment(), LoginView { ...@@ -443,6 +443,24 @@ class LoginFragment : Fragment(), LoginView {
} }
} }
override fun enableLoginByWordpress() {
ui {
button_wordpress.isClickable = true
}
}
override fun setupWordpressButtonListener(wordpressUrl: String, state: String) {
ui { activity ->
button_wordpress.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(wordpressUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun addCustomOauthServiceButton( override fun addCustomOauthServiceButton(
customOauthUrl: String, customOauthUrl: String,
state: String, state: String,
...@@ -488,11 +506,11 @@ class LoginFragment : Fragment(), LoginView { ...@@ -488,11 +506,11 @@ class LoginFragment : Fragment(), LoginView {
override fun setupFabListener() { override fun setupFabListener() {
ui { ui {
button_fab.isVisible = true button_fab.isVisible = true
button_fab.setOnClickListener({ button_fab.setOnClickListener {
button_fab.hide() button_fab.hide()
showRemainingSocialAccountsView() showRemainingSocialAccountsView()
scrollToBottom() scrollToBottom()
}) }
} }
} }
......
...@@ -104,6 +104,8 @@ class ServerFragment : Fragment(), ServerView { ...@@ -104,6 +104,8 @@ class ServerFragment : Fragment(), ServerView {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
// reset deep link info, so user can come back and log to another server...
deepLinkInfo = null
relative_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener) relative_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
} }
...@@ -144,9 +146,9 @@ class ServerFragment : Fragment(), ServerView { ...@@ -144,9 +146,9 @@ class ServerFragment : Fragment(), ServerView {
hideLoading() hideLoading()
AlertDialog.Builder(it) AlertDialog.Builder(it)
.setMessage(getString(R.string.msg_ver_not_recommended, BuildConfig.RECOMMENDED_SERVER_VERSION)) .setMessage(getString(R.string.msg_ver_not_recommended, BuildConfig.RECOMMENDED_SERVER_VERSION))
.setPositiveButton(R.string.msg_ok, { _, _ -> .setPositiveButton(R.string.msg_ok) { _, _ ->
performConnect() performConnect()
}) }
.create() .create()
.show() .show()
} }
......
...@@ -56,9 +56,7 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -56,9 +56,7 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
if (currentFragment != null) { currentFragment?.onActivityResult(requestCode, resultCode, data)
currentFragment.onActivityResult(requestCode, resultCode, data)
}
} }
override fun supportFragmentInjector(): AndroidInjector<Fragment> { override fun supportFragmentInjector(): AndroidInjector<Fragment> {
......
...@@ -6,6 +6,7 @@ import android.view.View ...@@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children import androidx.core.view.children
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.ui.bottomsheet.MessageActionsBottomSheet import chat.rocket.android.chatroom.ui.bottomsheet.MessageActionsBottomSheet
...@@ -94,6 +95,7 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>( ...@@ -94,6 +95,7 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
view.context?.let { view.context?.let {
if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) { if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) {
with(it.baseContext as AppCompatActivity) { with(it.baseContext as AppCompatActivity) {
if (this.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
val actionsBottomSheet = MessageActionsBottomSheet() val actionsBottomSheet = MessageActionsBottomSheet()
actionsBottomSheet.addItems(menuItems, this@BaseViewHolder) actionsBottomSheet.addItems(menuItems, this@BaseViewHolder)
actionsBottomSheet.show(supportFragmentManager, null) actionsBottomSheet.show(supportFragmentManager, null)
...@@ -104,6 +106,7 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>( ...@@ -104,6 +106,7 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
} }
} }
} }
}
internal fun setupActionMenu(view: View) { internal fun setupActionMenu(view: View) {
if (listener.isActionsEnabled()) { if (listener.isActionsEnabled()) {
......
...@@ -18,10 +18,9 @@ import java.security.InvalidParameterException ...@@ -18,10 +18,9 @@ import java.security.InvalidParameterException
class ChatRoomAdapter( class ChatRoomAdapter(
private val roomType: String? = null, private val roomType: String? = null,
private val roomName: String? = null, private val roomName: String? = null,
private val presenter: ChatRoomPresenter? = null, private val actionSelectListener: OnActionSelected? = null,
private val enableActions: Boolean = true, private val enableActions: Boolean = true,
private val reactionListener: EmojiReactionListener? = null, private val reactionListener: EmojiReactionListener? = null
private val context: Context? = null
) : RecyclerView.Adapter<BaseViewHolder<*>>() { ) : RecyclerView.Adapter<BaseViewHolder<*>>() {
private val dataSet = ArrayList<BaseUiModel<*>>() private val dataSet = ArrayList<BaseUiModel<*>>()
...@@ -70,7 +69,7 @@ class ChatRoomAdapter( ...@@ -70,7 +69,7 @@ class ChatRoomAdapter(
BaseUiModel.ViewType.MESSAGE_REPLY -> { BaseUiModel.ViewType.MESSAGE_REPLY -> {
val view = parent.inflate(R.layout.item_message_reply) val view = parent.inflate(R.layout.item_message_reply)
MessageReplyViewHolder(view, actionsListener, reactionListener) { roomName, permalink -> MessageReplyViewHolder(view, actionsListener, reactionListener) { roomName, permalink ->
presenter?.openDirectMessage(roomName, permalink) actionSelectListener?.openDirectMessage(roomName, permalink)
} }
} }
else -> { else -> {
...@@ -212,52 +211,53 @@ class ChatRoomAdapter( ...@@ -212,52 +211,53 @@ class ChatRoomAdapter(
message.apply { message.apply {
when (item.itemId) { when (item.itemId) {
R.id.action_message_info -> { R.id.action_message_info -> {
presenter?.messageInfo(id) actionSelectListener?.showMessageInfo(id)
} }
R.id.action_message_reply -> { R.id.action_message_reply -> {
if (roomName != null && roomType != null) { if (roomName != null && roomType != null) {
presenter?.citeMessage(roomName, roomType, id, true) actionSelectListener?.citeMessage(roomName, roomType, id, true)
} }
} }
R.id.action_message_quote -> { R.id.action_message_quote -> {
if (roomName != null && roomType != null) { if (roomName != null && roomType != null) {
presenter?.citeMessage(roomName, roomType, id, false) actionSelectListener?.citeMessage(roomName, roomType, id, false)
} }
} }
R.id.action_message_copy -> { R.id.action_message_copy -> {
presenter?.copyMessage(id) actionSelectListener?.copyMessage(id)
} }
R.id.action_message_edit -> { R.id.action_message_edit -> {
presenter?.editMessage(roomId, id, message.message) actionSelectListener?.editMessage(roomId, id, message.message)
} }
R.id.action_message_star -> { R.id.action_message_star -> {
if (!item.isChecked) { actionSelectListener?.toogleStar(id, !item.isChecked)
presenter?.starMessage(id)
} else {
presenter?.unstarMessage(id)
}
} }
R.id.action_message_unpin -> { R.id.action_message_unpin -> {
if (!item.isChecked) { actionSelectListener?.tooglePin(id, !item.isChecked)
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
} }
R.id.action_message_delete -> { R.id.action_message_delete -> {
context?.let { actionSelectListener?.deleteMessage(roomId, id)
val builder = AlertDialog.Builder(it)
builder.setTitle(it.getString(R.string.msg_delete_message))
.setMessage(it.getString(R.string.msg_delete_description))
.setPositiveButton(it.getString(R.string.msg_ok)) { _, _ -> presenter?.deleteMessage(roomId, id) }
.setNegativeButton(it.getString(R.string.msg_cancel)) { _, _ -> }
.show()
} }
R.id.action_menu_msg_react -> {
actionSelectListener?.showReactions(id)
}
else -> {
TODO("Not implemented")
} }
R.id.action_menu_msg_react -> presenter?.showReactions(id)
else -> TODO("Not implemented")
} }
} }
} }
} }
interface OnActionSelected {
fun showMessageInfo(id: String)
fun citeMessage(roomName: String, roomType: String, messageId: String, mentionAuthor: Boolean)
fun copyMessage(id: String)
fun editMessage(roomId: String, messageId: String, text: String)
fun toogleStar(id: String, star: Boolean)
fun tooglePin(id: String, pin: Boolean)
fun deleteMessage(roomId: String, id: String)
fun showReactions(id: String)
fun openDirectMessage(roomName: String, message: String)
}
} }
\ No newline at end of file
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.net.Uri
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import chat.rocket.android.chatroom.uimodel.UrlPreviewUiModel import chat.rocket.android.chatroom.uimodel.UrlPreviewUiModel
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.util.extensions.content import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.openTabbedUrl
import kotlinx.android.synthetic.main.message_url_preview.view.* import kotlinx.android.synthetic.main.message_url_preview.view.*
class UrlPreviewViewHolder(itemView: View, class UrlPreviewViewHolder(itemView: View,
...@@ -42,7 +41,7 @@ class UrlPreviewViewHolder(itemView: View, ...@@ -42,7 +41,7 @@ class UrlPreviewViewHolder(itemView: View,
private val onClickListener = { view: View -> private val onClickListener = { view: View ->
if (data != null) { if (data != null) {
view.openTabbedUrl(Uri.parse(data!!.rawData.url)) view.openTabbedUrl(data!!.rawData.url)
} }
} }
} }
\ No newline at end of file
...@@ -1006,7 +1006,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1006,7 +1006,7 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
private suspend fun subscribeTypingStatus() { private fun subscribeTypingStatus() {
launch(CommonPool + strategy.jobs) { launch(CommonPool + strategy.jobs) {
client.subscribeTypingStatus(chatRoomId.toString()) { _, id -> client.subscribeTypingStatus(chatRoomId.toString()) { _, id ->
typingStatusSubscriptionId = id typingStatusSubscriptionId = id
...@@ -1019,6 +1019,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1019,6 +1019,7 @@ class ChatRoomPresenter @Inject constructor(
} }
private fun processTypingStatus(typingStatus: Pair<String, Boolean>) { private fun processTypingStatus(typingStatus: Pair<String, Boolean>) {
if (typingStatus.first != currentLoggedUsername) {
if (!typingStatusList.any { username -> username == typingStatus.first }) { if (!typingStatusList.any { username -> username == typingStatus.first }) {
if (typingStatus.second) { if (typingStatus.second) {
typingStatusList.add(typingStatus.first) typingStatusList.add(typingStatus.first)
...@@ -1031,12 +1032,14 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1031,12 +1032,14 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
} }
if (typingStatusList.isNotEmpty()) { if (typingStatusList.isNotEmpty()) {
view.showTypingStatus(typingStatusList.toList()) // copy typingStatusList view.showTypingStatus(typingStatusList.toList())
} else { } else {
view.hideTypingStatusView() view.hideTypingStatusView()
} }
} }
}
private fun unsubscribeTypingStatus() { private fun unsubscribeTypingStatus() {
typingStatusSubscriptionId?.let { typingStatusSubscriptionId?.let {
......
...@@ -126,7 +126,8 @@ internal const val MENU_ACTION_PINNED_MESSAGES = 4 ...@@ -126,7 +126,8 @@ internal const val MENU_ACTION_PINNED_MESSAGES = 4
internal const val MENU_ACTION_FAVORITE_MESSAGES = 5 internal const val MENU_ACTION_FAVORITE_MESSAGES = 5
internal const val MENU_ACTION_FILES = 6 internal const val MENU_ACTION_FILES = 6
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener { class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener,
ChatRoomAdapter.OnActionSelected {
@Inject @Inject
lateinit var presenter: ChatRoomPresenter lateinit var presenter: ChatRoomPresenter
@Inject @Inject
...@@ -200,6 +201,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -200,6 +201,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} else { } else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
} }
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, this,
reactionListener = this)
} }
override fun onCreateView( override fun onCreateView(
...@@ -335,13 +339,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -335,13 +339,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
if (recycler_view.adapter == null) { if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(
chatRoomType,
chatRoomName,
presenter,
reactionListener = this@ChatRoomFragment,
context = context
)
recycler_view.adapter = adapter recycler_view.adapter = adapter
if (dataSet.size >= 30) { if (dataSet.size >= 30) {
recycler_view.addOnScrollListener(endlessRecyclerViewScrollListener) recycler_view.addOnScrollListener(endlessRecyclerViewScrollListener)
...@@ -967,4 +964,55 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -967,4 +964,55 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupToolbar(toolbarTitle: String) { private fun setupToolbar(toolbarTitle: String) {
(activity as ChatRoomActivity).showToolbarTitle(toolbarTitle) (activity as ChatRoomActivity).showToolbarTitle(toolbarTitle)
} }
override fun showMessageInfo(id: String) {
presenter.messageInfo(id)
}
override fun citeMessage(roomName: String, roomType: String, messageId: String, mentionAuthor: Boolean) {
presenter.citeMessage(roomName, roomType, messageId, mentionAuthor)
}
override fun copyMessage(id: String) {
presenter.copyMessage(id)
}
override fun editMessage(roomId: String, messageId: String, text: String) {
presenter.editMessage(roomId, messageId, text)
}
override fun toogleStar(id: String, star: Boolean) {
if (star) {
presenter.starMessage(id)
} else {
presenter.unstarMessage(id)
}
}
override fun tooglePin(id: String, pin: Boolean) {
if (pin) {
presenter.pinMessage(id)
} else {
presenter.unpinMessage(id)
}
}
override fun deleteMessage(roomId: String, id: String) {
ui {
val builder = AlertDialog.Builder(it)
builder.setTitle(it.getString(R.string.msg_delete_message))
.setMessage(it.getString(R.string.msg_delete_description))
.setPositiveButton(it.getString(R.string.msg_ok)) { _, _ -> presenter.deleteMessage(roomId, id) }
.setNegativeButton(it.getString(R.string.msg_cancel)) { _, _ -> }
.show()
}
}
override fun showReactions(id: String) {
presenter.showReactions(id)
}
override fun openDirectMessage(roomName: String, message: String) {
presenter.openDirectMessage(roomName, message)
}
} }
...@@ -35,7 +35,7 @@ private const val INTENT_CHAT_ROOM_ID = "chat_room_id" ...@@ -35,7 +35,7 @@ private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
private lateinit var adapter: ChatRoomAdapter private val adapter = ChatRoomAdapter(enableActions = false)
@Inject @Inject
lateinit var presenter: FavoriteMessagesPresenter lateinit var presenter: FavoriteMessagesPresenter
...@@ -66,7 +66,6 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { ...@@ -66,7 +66,6 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
override fun showFavoriteMessages(favoriteMessages: List<BaseUiModel<*>>) { override fun showFavoriteMessages(favoriteMessages: List<BaseUiModel<*>>) {
ui { ui {
if (recycler_view.adapter == null) { if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context) val linearLayoutManager = LinearLayoutManager(context)
recycler_view.layoutManager = linearLayoutManager recycler_view.layoutManager = linearLayoutManager
......
...@@ -5,21 +5,19 @@ import android.content.Context ...@@ -5,21 +5,19 @@ import android.content.Context
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 androidx.core.content.res.ResourcesCompat
import android.text.Spanned import android.text.Spanned
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.text.style.ReplacementSpan import android.text.style.ReplacementSpan
import android.text.style.StyleSpan
import android.util.Patterns import android.util.Patterns
import android.view.View import android.view.View
import androidx.core.content.res.ResourcesCompat
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.util.extensions.openTabbedUrl
import chat.rocket.android.emoji.EmojiParser import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.emoji.EmojiTypefaceSpan import chat.rocket.android.emoji.EmojiTypefaceSpan
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.common.model.SimpleUser import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import org.commonmark.node.AbstractVisitor import org.commonmark.node.AbstractVisitor
...@@ -159,7 +157,7 @@ class MessageParser @Inject constructor( ...@@ -159,7 +157,7 @@ class MessageParser @Inject constructor(
if (node is ListItem) { if (node is ListItem) {
newLine() newLine()
builder.append("$number$delimiter ") builder.append("$number$delimiter ")
super.visit(node.firstChild as Paragraph) super.visitChildren(node.firstChild)
newLine() newLine()
} }
number++ number++
...@@ -187,7 +185,7 @@ class MessageParser @Inject constructor( ...@@ -187,7 +185,7 @@ class MessageParser @Inject constructor(
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) {
view.openTabbedUrl(getUri(link)) view.openTabbedUrl(link)
} }
}, matcher.start(0), matcher.end(0)) }, matcher.start(0), matcher.end(0))
consumed.add(link) consumed.add(link)
...@@ -195,14 +193,6 @@ class MessageParser @Inject constructor( ...@@ -195,14 +193,6 @@ class MessageParser @Inject constructor(
} }
visitChildren(text) visitChildren(text)
} }
private fun getUri(link: String): Uri {
val uri = Uri.parse(link)
if (uri.scheme == null) {
return Uri.parse("http://$link")
}
return uri
}
} }
class MentionSpan( class MentionSpan(
......
...@@ -92,6 +92,59 @@ object OauthHelper { ...@@ -92,6 +92,59 @@ object OauthHelper {
"&scope=email" "&scope=email"
} }
/**
* Returns the WordPress-Com Oauth URL.
*
* @param clientId The WordPress-Com client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The WordPress-Com Oauth URL.
*/
fun getWordpressComOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://public-api.wordpress.com/oauth2/authorize" +
"?client_id=$clientId" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/wordpress?close" +
"&state=$state" +
"&response_type=code" +
"&scope=auth"
}
/**
* Returns the WordPress custom Oauth URL.
*
* @param host The WordPress custom OAuth host.
* @param authorizePath The WordPress custom OAuth authorization path.
* @param clientId The WordPress 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 WordPress custom OAuth scope.
* @return The WordPress custom Oauth URL.
*/
fun getWordpressCustomOauthUrl(
host: String,
authorizePath: String,
clientId: String,
serverUrl: String,
serviceName: String,
state: String,
scope: String
): String {
(authorizePath +
"?client_id=$clientId" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/$serviceName?close" +
"&state=$state" +
"&scope=$scope" +
"&response_type=code"
).let {
return if (it.contains(host)) {
it
} else {
host + it
}
}
}
/** /**
* Returns the Custom Oauth URL. * Returns the Custom Oauth URL.
* *
......
...@@ -5,6 +5,7 @@ import chat.rocket.android.db.DatabaseManagerFactory ...@@ -5,6 +5,7 @@ import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.main.uimodel.NavHeaderUiModel import chat.rocket.android.main.uimodel.NavHeaderUiModel
import chat.rocket.android.main.uimodel.NavHeaderUiModelMapper import chat.rocket.android.main.uimodel.NavHeaderUiModelMapper
import chat.rocket.android.push.GroupedPush
import chat.rocket.android.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
...@@ -52,6 +53,7 @@ class MainPresenter @Inject constructor( ...@@ -52,6 +53,7 @@ class MainPresenter @Inject constructor(
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
private val removeAccountInteractor: RemoveAccountInteractor, private val removeAccountInteractor: RemoveAccountInteractor,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val groupedPush: GroupedPush,
dbManagerFactory: DatabaseManagerFactory, dbManagerFactory: DatabaseManagerFactory,
getSettingsInteractor: GetSettingsInteractor, getSettingsInteractor: GetSettingsInteractor,
managerFactory: ConnectionManagerFactory managerFactory: ConnectionManagerFactory
...@@ -232,4 +234,12 @@ class MainPresenter @Inject constructor( ...@@ -232,4 +234,12 @@ class MainPresenter @Inject constructor(
private fun updateMyself(myself: Myself) = private fun updateMyself(myself: Myself) =
view.setupUserAccountInfo(navHeaderMapper.mapToUiModel(myself)) view.setupUserAccountInfo(navHeaderMapper.mapToUiModel(myself))
fun clearNotificationsForChatroom(chatRoomId: String?) {
if (chatRoomId == null) return
groupedPush.hostToPushMessageList[currentServer]?.let { list ->
list.removeAll { it.info.roomId == chatRoomId }
}
}
} }
...@@ -5,13 +5,13 @@ import android.app.Activity ...@@ -5,13 +5,13 @@ import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.app.ProgressDialog import android.app.ProgressDialog
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Gravity import android.view.Gravity
import android.view.MenuItem import android.view.MenuItem
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.main.adapter.AccountsAdapter import chat.rocket.android.main.adapter.AccountsAdapter
...@@ -55,7 +55,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, ...@@ -55,7 +55,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
private var expanded = false private var expanded = false
private val headerLayout by lazy { view_navigation.getHeaderView(0) } private val headerLayout by lazy { view_navigation.getHeaderView(0) }
private var chatRoomId: String? = null private var chatRoomId: String? = null
private var progressDialog : ProgressDialog? = null private var progressDialog: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this) AndroidInjection.inject(this)
...@@ -74,6 +74,9 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, ...@@ -74,6 +74,9 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
println("ChatRoomId: $chatRoomId")
presenter.clearNotificationsForChatroom(chatRoomId)
presenter.connect() presenter.connect()
presenter.loadServerAccounts() presenter.loadServerAccounts()
presenter.loadCurrentInfo() presenter.loadCurrentInfo()
......
...@@ -36,7 +36,7 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id" ...@@ -36,7 +36,7 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
class MentionsFragment : Fragment(), MentionsView { class MentionsFragment : Fragment(), MentionsView {
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
private lateinit var adapter: ChatRoomAdapter private val adapter = ChatRoomAdapter(enableActions = false)
@Inject @Inject
lateinit var presenter: MentionsPresenter lateinit var presenter: MentionsPresenter
...@@ -68,7 +68,6 @@ class MentionsFragment : Fragment(), MentionsView { ...@@ -68,7 +68,6 @@ class MentionsFragment : Fragment(), MentionsView {
override fun showMentions(mentions: List<BaseUiModel<*>>) { override fun showMentions(mentions: List<BaseUiModel<*>>) {
ui { ui {
if (recycler_view.adapter == null) { if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context) val linearLayoutManager = LinearLayoutManager(context)
......
...@@ -36,7 +36,7 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id" ...@@ -36,7 +36,7 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
class PinnedMessagesFragment : Fragment(), PinnedMessagesView { class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
private lateinit var adapter: ChatRoomAdapter private val adapter = ChatRoomAdapter(enableActions = false)
@Inject @Inject
lateinit var presenter: PinnedMessagesPresenter lateinit var presenter: PinnedMessagesPresenter
...@@ -68,7 +68,6 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { ...@@ -68,7 +68,6 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
override fun showPinnedMessages(pinnedMessages: List<BaseUiModel<*>>) { override fun showPinnedMessages(pinnedMessages: List<BaseUiModel<*>>) {
ui { ui {
if (recycler_view_pinned.adapter == null) { if (recycler_view_pinned.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false)
recycler_view_pinned.adapter = adapter recycler_view_pinned.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context) val linearLayoutManager = LinearLayoutManager(context)
......
...@@ -18,6 +18,7 @@ import androidx.core.app.NotificationManagerCompat ...@@ -18,6 +18,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput import androidx.core.app.RemoteInput
import android.text.Html import android.text.Html
import android.text.Spanned import android.text.Spanned
import androidx.core.content.ContextCompat
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.domain.GetAccountInteractor import chat.rocket.android.server.domain.GetAccountInteractor
...@@ -36,10 +37,6 @@ import java.util.* ...@@ -36,10 +37,6 @@ import java.util.*
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject import javax.inject.Inject
/**
* Refer to: https://github.com/RocketChat/Rocket.Chat.Android/blob/9e846b7fde8fe0c74b9e0117c37ce49293308db5/app/src/main/java/chat/rocket/android/push/PushManager.kt
* for old source code.
*/
class PushManager @Inject constructor( class PushManager @Inject constructor(
private val groupedPushes: GroupedPush, private val groupedPushes: GroupedPush,
private val manager: NotificationManager, private val manager: NotificationManager,
...@@ -80,7 +77,7 @@ class PushManager @Inject constructor( ...@@ -80,7 +77,7 @@ class PushManager @Inject constructor(
showNotification(pushMessage) showNotification(pushMessage)
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error parsing PUSH message: $data") Timber.e(ex, "Error parsing PUSH message: $data")
ex.printStackTrace() ex.printStackTrace()
} }
} }
...@@ -101,7 +98,7 @@ class PushManager @Inject constructor( ...@@ -101,7 +98,7 @@ class PushManager @Inject constructor(
val groupTuple = getGroupForHost(host) val groupTuple = getGroupForHost(host)
groupTuple.second.incrementAndGet() groupTuple.second.incrementAndGet()
val notIdListForHostname: MutableList<PushMessage>? = groupedPushes.hostToPushMessageList.get(host) val notIdListForHostname: MutableList<PushMessage>? = groupedPushes.hostToPushMessageList[host]
if (notIdListForHostname == null) { if (notIdListForHostname == null) {
groupedPushes.hostToPushMessageList[host] = arrayListOf(pushMessage) groupedPushes.hostToPushMessageList[host] = arrayListOf(pushMessage)
} else { } else {
...@@ -365,14 +362,14 @@ class PushManager @Inject constructor( ...@@ -365,14 +362,14 @@ class PushManager @Inject constructor(
val res = context.resources val res = context.resources
val smallIcon = res.getIdentifier( val smallIcon = res.getIdentifier(
"rocket_chat_notification", "drawable", context.packageName) "rocket_chat_notification", "drawable", context.packageName)
with(this, { with(this) {
setAutoCancel(true) setAutoCancel(true)
setShowWhen(true) setShowWhen(true)
color = context.resources.getColor(R.color.colorPrimary) color = ContextCompat.getColor(context, R.color.colorPrimary)
setDefaults(Notification.DEFAULT_ALL) setDefaults(Notification.DEFAULT_ALL)
setSmallIcon(smallIcon) setSmallIcon(smallIcon)
setSound(alarmSound) setSound(alarmSound)
}) }
return this return this
} }
} }
......
...@@ -4,7 +4,6 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -4,7 +4,6 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.settings import chat.rocket.core.internal.rest.settings
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
import timber.log.Timber import timber.log.Timber
...@@ -16,24 +15,61 @@ class RefreshSettingsInteractor @Inject constructor( ...@@ -16,24 +15,61 @@ class RefreshSettingsInteractor @Inject constructor(
) { ) {
private var settingsFilter = arrayOf( private var settingsFilter = arrayOf(
LDAP_ENABLE, CAS_ENABLE, CAS_LOGIN_URL, LDAP_ENABLE,
CAS_ENABLE,
CAS_LOGIN_URL,
ACCOUNT_REGISTRATION, ACCOUNT_LOGIN_FORM, ACCOUNT_PASSWORD_RESET, ACCOUNT_CUSTOM_FIELDS, ACCOUNT_REGISTRATION,
ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR, ACCOUNT_LOGIN_FORM,
ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, ACCOUNT_GITLAB, ACCOUNT_GITLAB_URL, ACCOUNT_PASSWORD_RESET,
ACCOUNT_CUSTOM_FIELDS,
ACCOUNT_GOOGLE,
ACCOUNT_FACEBOOK,
ACCOUNT_GITHUB,
ACCOUNT_LINKEDIN,
ACCOUNT_METEOR,
ACCOUNT_TWITTER,
ACCOUNT_GITLAB,
ACCOUNT_GITLAB_URL,
ACCOUNT_WORDPRESS,
ACCOUNT_WORDPRESS_URL,
SITE_URL, SITE_NAME, FAVICON_512, FAVICON_196, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS, SITE_URL,
FAVORITE_ROOMS, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE, UPLOAD_WHITELIST_MIMETYPES, SITE_NAME,
HIDE_USER_JOIN, HIDE_USER_LEAVE, FAVICON_512,
HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING, FAVICON_196,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, ALLOW_MESSAGE_STARRING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS, USE_REALNAME,
WIDE_TILE_310, STORE_LAST_MESSAGE, MESSAGE_READ_RECEIPT_ENABLED, MESSAGE_READ_RECEIPT_STORE_USERS) ALLOW_ROOM_NAME_SPECIAL_CHARS,
FAVORITE_ROOMS,
UPLOAD_STORAGE_TYPE,
UPLOAD_MAX_FILE_SIZE,
UPLOAD_WHITELIST_MIMETYPES,
HIDE_USER_JOIN,
HIDE_USER_LEAVE,
HIDE_TYPE_AU,
HIDE_MUTE_UNMUTE,
HIDE_TYPE_RU,
ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING,
ALLOW_MESSAGE_PINNING,
ALLOW_MESSAGE_STARRING,
SHOW_DELETED_STATUS,
SHOW_EDITED_STATUS,
WIDE_TILE_310,
STORE_LAST_MESSAGE,
MESSAGE_READ_RECEIPT_ENABLED,
MESSAGE_READ_RECEIPT_STORE_USERS
)
suspend fun refresh(server: String) { suspend fun refresh(server: String) {
withContext(CommonPool) { withContext(CommonPool) {
factory.create(server).let { client -> factory.create(server).let { client ->
val settings = retryIO(description = "settings", times = 5, val settings = retryIO(
maxDelay = 5000, initialDelay = 300) { description = "settings",
times = 5,
maxDelay = 5000,
initialDelay = 300
) {
client.settings(*settingsFilter) client.settings(*settingsFilter)
} }
repository.save(server, settings) repository.save(server, settings)
......
...@@ -19,9 +19,10 @@ const val ACCOUNT_GITHUB = "Accounts_OAuth_Github" ...@@ -19,9 +19,10 @@ const val ACCOUNT_GITHUB = "Accounts_OAuth_Github"
const val ACCOUNT_LINKEDIN = "Accounts_OAuth_Linkedin" const val ACCOUNT_LINKEDIN = "Accounts_OAuth_Linkedin"
const val ACCOUNT_METEOR = "Accounts_OAuth_Meteor" const val ACCOUNT_METEOR = "Accounts_OAuth_Meteor"
const val ACCOUNT_TWITTER = "Accounts_OAuth_Twitter" const val ACCOUNT_TWITTER = "Accounts_OAuth_Twitter"
const val ACCOUNT_WORDPRESS = "Accounts_OAuth_Wordpress"
const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab" const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab"
const val ACCOUNT_GITLAB_URL = "API_Gitlab_URL" const val ACCOUNT_GITLAB_URL = "API_Gitlab_URL"
const val ACCOUNT_WORDPRESS = "Accounts_OAuth_Wordpress"
const val ACCOUNT_WORDPRESS_URL = "API_Wordpress_URL"
const val SITE_URL = "Site_Url" const val SITE_URL = "Site_Url"
const val SITE_NAME = "Site_Name" const val SITE_NAME = "Site_Name"
...@@ -71,6 +72,7 @@ fun PublicSettings.isTwitterAuthenticationEnabled(): Boolean = this[ACCOUNT_TWIT ...@@ -71,6 +72,7 @@ fun PublicSettings.isTwitterAuthenticationEnabled(): Boolean = this[ACCOUNT_TWIT
fun PublicSettings.isGitlabAuthenticationEnabled(): Boolean = this[ACCOUNT_GITLAB]?.value == true fun PublicSettings.isGitlabAuthenticationEnabled(): Boolean = this[ACCOUNT_GITLAB]?.value == true
fun PublicSettings.gitlabUrl(): String? = this[ACCOUNT_GITLAB_URL]?.value as String? fun PublicSettings.gitlabUrl(): String? = this[ACCOUNT_GITLAB_URL]?.value as String?
fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true
fun PublicSettings.wordpressUrl(): String? = this[ACCOUNT_WORDPRESS_URL]?.value as String?
fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true
fun PublicSettings.useSpecialCharsOnRoom(): Boolean = this[ALLOW_ROOM_NAME_SPECIAL_CHARS]?.value == true fun PublicSettings.useSpecialCharsOnRoom(): Boolean = this[ALLOW_ROOM_NAME_SPECIAL_CHARS]?.value == true
......
...@@ -3,6 +3,7 @@ package chat.rocket.android.util.extensions ...@@ -3,6 +3,7 @@ package chat.rocket.android.util.extensions
import android.graphics.Color import android.graphics.Color
import android.util.Patterns import android.util.Patterns
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
import okhttp3.HttpUrl
import timber.log.Timber import timber.log.Timber
fun String.removeTrailingSlash(): String { fun String.removeTrailingSlash(): String {
...@@ -65,3 +66,10 @@ fun String.parseColor(): Int { ...@@ -65,3 +66,10 @@ fun String.parseColor(): Int {
fun String.userId(userId: String?): String? { fun String.userId(userId: String?): String? {
return userId?.let { this.replace(it, "") } return userId?.let { this.replace(it, "") }
} }
fun String.lowercaseUrl(): String? {
val httpUrl = HttpUrl.parse(this)
val newScheme = httpUrl?.scheme()?.toLowerCase()
return httpUrl?.newBuilder()?.scheme(newScheme)?.build()?.toString()
}
\ No newline at end of file
...@@ -7,15 +7,27 @@ import android.view.View ...@@ -7,15 +7,27 @@ import android.view.View
import chat.rocket.android.R import chat.rocket.android.R
import timber.log.Timber import timber.log.Timber
fun View.openTabbedUrl(url: Uri) { fun View.openTabbedUrl(url: String) {
with(this) { with(this) {
val uri = url.ensureScheme()
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()
try { try {
customTabsIntent.launchUrl(context, url) customTabsIntent.launchUrl(context, uri)
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Unable to launch URL") Timber.d(ex, "Unable to launch URL")
} }
} }
} }
private fun String.ensureScheme(): Uri? {
// check if the URL starts with a http(s) scheme
val url = if (!this.matches(Regex("^([h|H][t|T][t|T][p|P]).*"))) {
"http://$this"
} else {
this
}
return Uri.parse(url.lowercaseUrl())
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="290dp"
android:height="40dp"
android:viewportWidth="290"
android:viewportHeight="40">
<path
android:fillColor="#428BBA"
android:fillType="evenOdd"
android:pathData="M2,0L288,0A2,2 0,0 1,290 2L290,38A2,2 0,0 1,288 40L2,40A2,2 0,0 1,0 38L0,2A2,2 0,0 1,2 0z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#FFFFFFFF"
android:fillType="evenOdd"
android:pathData="M134.878,23.188L136.628,14.625L138.589,14.625L135.964,26L134.073,26L131.909,17.695L129.698,26L127.8,26L125.175,14.625L127.136,14.625L128.901,23.172L131.073,14.625L132.729,14.625L134.878,23.188ZM139.824,21.695C139.824,20.867 139.988,20.121 140.316,19.457C140.645,18.793 141.105,18.283 141.699,17.926C142.293,17.569 142.975,17.391 143.746,17.391C144.887,17.391 145.813,17.758 146.523,18.492C147.234,19.227 147.618,20.201 147.676,21.414L147.684,21.859C147.684,22.693 147.523,23.437 147.203,24.094C146.883,24.75 146.424,25.258 145.828,25.617C145.232,25.977 144.543,26.156 143.762,26.156C142.569,26.156 141.615,25.759 140.898,24.965C140.182,24.171 139.824,23.112 139.824,21.789L139.824,21.695ZM141.723,21.859C141.723,22.729 141.902,23.41 142.262,23.902C142.621,24.395 143.121,24.641 143.762,24.641C144.402,24.641 144.901,24.391 145.258,23.891C145.615,23.391 145.793,22.659 145.793,21.695C145.793,20.841 145.609,20.164 145.242,19.664C144.875,19.164 144.376,18.914 143.746,18.914C143.126,18.914 142.634,19.16 142.27,19.652C141.905,20.145 141.723,20.88 141.723,21.859ZM154.286,19.281C154.036,19.24 153.778,19.219 153.513,19.219C152.643,19.219 152.057,19.552 151.755,20.219L151.755,26L149.857,26L149.857,17.547L151.669,17.547L151.716,18.492C152.174,17.758 152.81,17.391 153.622,17.391C153.893,17.391 154.117,17.427 154.294,17.5L154.286,19.281ZM155.553,21.711C155.553,20.409 155.855,19.363 156.459,18.574C157.063,17.785 157.873,17.391 158.889,17.391C159.785,17.391 160.509,17.703 161.061,18.328L161.061,14L162.959,14L162.959,26L161.241,26L161.147,25.125C160.579,25.813 159.821,26.156 158.873,26.156C157.884,26.156 157.083,25.758 156.471,24.961C155.859,24.164 155.553,23.081 155.553,21.711ZM157.451,21.875C157.451,22.734 157.617,23.405 157.948,23.887C158.278,24.368 158.748,24.609 159.358,24.609C160.134,24.609 160.701,24.263 161.061,23.57L161.061,19.961C160.712,19.284 160.149,18.945 159.373,18.945C158.759,18.945 158.285,19.189 157.951,19.676C157.618,20.163 157.451,20.896 157.451,21.875ZM167.671,21.773L167.671,26L165.695,26L165.695,14.625L170.046,14.625C171.317,14.625 172.326,14.956 173.074,15.617C173.821,16.279 174.195,17.154 174.195,18.242C174.195,19.357 173.829,20.224 173.097,20.844C172.365,21.464 171.341,21.773 170.023,21.773L167.671,21.773ZM167.671,20.188L170.046,20.188C170.749,20.188 171.286,20.022 171.656,19.691C172.025,19.361 172.21,18.883 172.21,18.258C172.21,17.643 172.023,17.152 171.648,16.785C171.273,16.418 170.757,16.229 170.101,16.219L167.671,16.219L167.671,20.188ZM180.735,19.281C180.485,19.24 180.227,19.219 179.962,19.219C179.092,19.219 178.506,19.552 178.204,20.219L178.204,26L176.305,26L176.305,17.547L178.118,17.547L178.165,18.492C178.623,17.758 179.258,17.391 180.071,17.391C180.342,17.391 180.566,17.427 180.743,17.5L180.735,19.281ZM186.08,26.156C184.877,26.156 183.901,25.777 183.154,25.02C182.407,24.262 182.033,23.253 182.033,21.992L182.033,21.758C182.033,20.914 182.196,20.16 182.521,19.496C182.847,18.832 183.304,18.315 183.892,17.945C184.481,17.576 185.137,17.391 185.861,17.391C187.012,17.391 187.901,17.758 188.529,18.492C189.157,19.227 189.47,20.266 189.47,21.609L189.47,22.375L183.947,22.375C184.004,23.073 184.237,23.625 184.646,24.031C185.055,24.438 185.569,24.641 186.189,24.641C187.059,24.641 187.767,24.289 188.314,23.586L189.338,24.563C188.999,25.068 188.547,25.46 187.982,25.738C187.417,26.017 186.783,26.156 186.08,26.156ZM185.853,18.914C185.332,18.914 184.912,19.096 184.592,19.461C184.271,19.826 184.067,20.333 183.978,20.984L187.595,20.984L187.595,20.844C187.554,20.208 187.384,19.728 187.088,19.402C186.791,19.077 186.379,18.914 185.853,18.914ZM196.253,23.703C196.253,23.365 196.113,23.107 195.835,22.93C195.556,22.753 195.094,22.596 194.448,22.461C193.802,22.326 193.263,22.154 192.831,21.945C191.883,21.487 191.409,20.823 191.409,19.953C191.409,19.224 191.716,18.615 192.331,18.125C192.945,17.635 193.727,17.391 194.675,17.391C195.685,17.391 196.501,17.641 197.124,18.141C197.746,18.641 198.057,19.289 198.057,20.086L196.159,20.086C196.159,19.721 196.024,19.418 195.753,19.176C195.482,18.934 195.123,18.813 194.675,18.813C194.258,18.813 193.918,18.909 193.655,19.102C193.392,19.294 193.261,19.552 193.261,19.875C193.261,20.167 193.383,20.393 193.628,20.555C193.873,20.716 194.367,20.879 195.112,21.043C195.857,21.207 196.442,21.402 196.866,21.629C197.291,21.855 197.606,22.128 197.811,22.445C198.017,22.763 198.12,23.148 198.12,23.602C198.12,24.362 197.805,24.978 197.175,25.449C196.544,25.921 195.719,26.156 194.698,26.156C194.005,26.156 193.388,26.031 192.847,25.781C192.305,25.531 191.883,25.188 191.581,24.75C191.279,24.312 191.128,23.841 191.128,23.336L192.972,23.336C192.998,23.784 193.167,24.129 193.479,24.371C193.792,24.613 194.206,24.734 194.722,24.734C195.222,24.734 195.602,24.639 195.862,24.449C196.123,24.259 196.253,24.01 196.253,23.703ZM205.082,23.703C205.082,23.365 204.943,23.107 204.664,22.93C204.385,22.753 203.923,22.596 203.277,22.461C202.632,22.326 202.092,22.154 201.66,21.945C200.712,21.487 200.238,20.823 200.238,19.953C200.238,19.224 200.546,18.615 201.16,18.125C201.775,17.635 202.556,17.391 203.504,17.391C204.514,17.391 205.331,17.641 205.953,18.141C206.576,18.641 206.887,19.289 206.887,20.086L204.988,20.086C204.988,19.721 204.853,19.418 204.582,19.176C204.311,18.934 203.952,18.813 203.504,18.813C203.087,18.813 202.747,18.909 202.484,19.102C202.221,19.294 202.09,19.552 202.09,19.875C202.09,20.167 202.212,20.393 202.457,20.555C202.702,20.716 203.197,20.879 203.941,21.043C204.686,21.207 205.271,21.402 205.695,21.629C206.12,21.855 206.435,22.128 206.641,22.445C206.846,22.763 206.949,23.148 206.949,23.602C206.949,24.362 206.634,24.978 206.004,25.449C205.374,25.921 204.548,26.156 203.527,26.156C202.835,26.156 202.217,26.031 201.676,25.781C201.134,25.531 200.712,25.188 200.41,24.75C200.108,24.312 199.957,23.841 199.957,23.336L201.801,23.336C201.827,23.784 201.996,24.129 202.309,24.371C202.621,24.613 203.035,24.734 203.551,24.734C204.051,24.734 204.431,24.639 204.691,24.449C204.952,24.259 205.082,24.01 205.082,23.703Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:pathData="M104.26,11C99.146,11 95,15.146 95,20.26C95,25.375 99.146,29.521 104.26,29.521C109.375,29.521 113.521,25.375 113.521,20.26C113.521,15.146 109.375,11 104.26,11M104.26,11.556C105.995,11.552 107.69,12.07 109.127,13.042C110.054,13.669 110.852,14.467 111.479,15.394C112.451,16.83 112.969,18.526 112.965,20.26C112.969,21.995 112.451,23.69 111.479,25.127C110.852,26.054 110.054,26.852 109.127,27.479C107.69,28.451 105.995,28.969 104.26,28.965C102.526,28.969 100.83,28.451 99.394,27.479C98.467,26.852 97.669,26.054 97.042,25.127C96.07,23.69 95.552,21.995 95.556,20.26C95.552,18.526 96.07,16.83 97.042,15.394C97.669,14.467 98.467,13.669 99.393,13.042C100.83,12.07 102.526,11.552 104.26,11.556"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:pathData="M111.032,16.558C111.066,16.804 111.084,17.068 111.084,17.351C111.084,18.134 110.937,19.014 110.497,20.115L108.14,26.93C110.516,25.549 111.978,23.008 111.977,20.26C111.979,18.966 111.654,17.693 111.032,16.559L111.032,16.558ZM104.396,20.935L102.08,27.663C103.635,28.121 105.294,28.078 106.823,27.54C106.802,27.506 106.783,27.471 106.768,27.434L104.396,20.935ZM109.47,19.871C109.47,18.917 109.128,18.257 108.834,17.743C108.443,17.106 108.076,16.569 108.076,15.933C108.076,15.223 108.613,14.563 109.372,14.563C109.406,14.563 109.438,14.567 109.472,14.569C108.05,13.264 106.19,12.541 104.26,12.544C101.662,12.543 99.238,13.851 97.813,16.022C97.994,16.028 98.165,16.032 98.309,16.032C99.116,16.032 100.366,15.934 100.366,15.934C100.782,15.909 100.831,16.52 100.415,16.57C100.415,16.57 99.997,16.618 99.532,16.643L102.343,25.002L104.031,19.937L102.829,16.643C102.414,16.618 102.02,16.57 102.02,16.57C101.604,16.545 101.652,15.909 102.068,15.934C102.068,15.934 103.342,16.032 104.101,16.032C104.908,16.032 106.158,15.934 106.158,15.934C106.574,15.909 106.623,16.52 106.207,16.57C106.207,16.57 105.788,16.618 105.324,16.643L108.113,24.938L108.908,22.415C109.263,21.313 109.47,20.531 109.47,19.872L109.47,19.871ZM96.544,20.26C96.544,23.217 98.233,25.915 100.893,27.205L97.211,17.12C96.77,18.108 96.543,19.178 96.544,20.26Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
...@@ -218,6 +218,18 @@ ...@@ -218,6 +218,18 @@
android:src="@drawable/ic_gitlab" android:src="@drawable/ic_gitlab"
android:visibility="gone" android:visibility="gone"
tools:visibility="gone" /> tools:visibility="gone" />
<ImageButton
android:id="@+id/button_wordpress"
android:layout_width="290dp"
android:layout_height="40dp"
android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_gitlab"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_wordpress"
android:visibility="gone"
tools:visibility="gone" />
</LinearLayout> </LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
......
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<string name="action_drawing">dibujo</string> <string name="action_drawing">dibujo</string>
<string name="action_save_to_gallery">Guardar en la galería</string> <string name="action_save_to_gallery">Guardar en la galería</string>
<string name="action_share">Compartir</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -78,6 +77,7 @@ ...@@ -78,6 +77,7 @@
<string name="msg_content_description_log_in_using_meteor">Inicia sesión usando 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">Inicia sesión usando 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">Inicia sesión usando Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Inicia sesión usando Gitlab</string>
<string name="msg_content_description_log_in_using_wordprees">Login using WordPress</string> <!-- TODO Translate-->
<string name="msg_content_description_send_message">Enviar mensaje</string> <string name="msg_content_description_send_message">Enviar mensaje</string>
<string name="msg_content_description_show_attachment_options">Mostrar opciones de archivo adjunto</string> <string name="msg_content_description_show_attachment_options">Mostrar opciones de archivo adjunto</string>
<string name="msg_you"></string> <string name="msg_you"></string>
...@@ -275,4 +275,5 @@ ...@@ -275,4 +275,5 @@
<string name="msg_file_description">Descripción del archivo</string> <string name="msg_file_description">Descripción del archivo</string>
<string name="msg_send">Enviar</string> <string name="msg_send">Enviar</string>
<string name="msg_sent_attachment">Envió un archivo</string> <string name="msg_sent_attachment">Envió un archivo</string>
</resources> </resources>
...@@ -85,6 +85,7 @@ ...@@ -85,6 +85,7 @@
<string name="msg_content_description_log_in_using_meteor">Connectez-vous en utilisant 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">Connectez-vous en utilisant 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">Connectez-vous en utilisant Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Connectez-vous en utilisant Gitlab</string>
<string name="msg_content_description_log_in_using_wordprees">Login using WordPress</string> <!-- TODO Translate-->
<string name="msg_content_description_send_message">Envoyer message</string> <string name="msg_content_description_send_message">Envoyer message</string>
<string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string> <string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string>
<string name="msg_you">Toi</string> <string name="msg_you">Toi</string>
......
...@@ -79,6 +79,7 @@ ...@@ -79,6 +79,7 @@
<string name="msg_content_description_log_in_using_meteor">Meteor द्वारा लॉगिन करें</string> <string name="msg_content_description_log_in_using_meteor">Meteor द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_twitter">Twitter द्वारा लॉगिन करें</string> <string name="msg_content_description_log_in_using_twitter">Twitter द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_gitlab">Gitlab द्वारा लॉगिन करें</string> <string name="msg_content_description_log_in_using_gitlab">Gitlab द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_wordprees">Login using WordPress</string> <!-- TODO Translate-->
<string name="msg_content_description_send_message">मेसेज भेजें</string> <string name="msg_content_description_send_message">मेसेज भेजें</string>
<string name="msg_content_description_show_attachment_options">अटैचमेंट विकल्प दिखाएं</string> <string name="msg_content_description_show_attachment_options">अटैचमेंट विकल्प दिखाएं</string>
<string name="msg_you">आप</string> <string name="msg_you">आप</string>
......
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
<string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string> <string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string> <string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string>
<string name="msg_content_description_log_in_using_wordprees">Fazer login através do WordPress</string>
<string name="msg_content_description_send_message">Enviar mensagem</string> <string name="msg_content_description_send_message">Enviar mensagem</string>
<string name="msg_content_description_show_attachment_options">Mostrar opções de anexo</string> <string name="msg_content_description_show_attachment_options">Mostrar opções de anexo</string>
<string name="msg_you">Você</string> <string name="msg_you">Você</string>
......
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
<string name="msg_content_description_log_in_using_meteor">Войти используя Meteor</string> <string name="msg_content_description_log_in_using_meteor">Войти используя Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Войти используя Twitter</string> <string name="msg_content_description_log_in_using_twitter">Войти используя Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Войти используя Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Войти используя Gitlab</string>
<string name="msg_content_description_log_in_using_wordprees">Login using WordPress</string> <!-- TODO Translate-->
<string name="msg_content_description_send_message">Отправить сообщение</string> <string name="msg_content_description_send_message">Отправить сообщение</string>
<string name="msg_content_description_show_attachment_options">Показать параметры вложения</string> <string name="msg_content_description_show_attachment_options">Показать параметры вложения</string>
<string name="msg_you">Вы</string> <string name="msg_you">Вы</string>
......
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string> <string name="msg_content_description_log_in_using_meteor">Login using Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string> <string name="msg_content_description_log_in_using_twitter">Login using Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_content_description_log_in_using_wordprees">Login using WordPress</string>
<string name="msg_content_description_send_message">Send message</string> <string name="msg_content_description_send_message">Send message</string>
<string name="msg_content_description_show_attachment_options">Show attachment options</string> <string name="msg_content_description_show_attachment_options">Show attachment options</string>
<string name="msg_you">You</string> <string name="msg_you">You</string>
......
...@@ -18,6 +18,11 @@ android { ...@@ -18,6 +18,11 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
packagingOptions {
exclude 'META-INF/core.kotlin_module'
exclude 'META-INF/main.kotlin_module'
}
} }
dependencies { dependencies {
......
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