Commit b6849d3a authored by Leonardo Aramaki's avatar Leonardo Aramaki

Merge branch 'develop' into new/download-received-pictures

parents 4508749d dc5b109e
...@@ -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
......
...@@ -18,6 +18,7 @@ import chat.rocket.android.app.migration.model.RealmSession ...@@ -18,6 +18,7 @@ import chat.rocket.android.app.migration.model.RealmSession
import chat.rocket.android.app.migration.model.RealmUser import chat.rocket.android.app.migration.model.RealmUser
import chat.rocket.android.authentication.domain.model.toToken import chat.rocket.android.authentication.domain.model.toToken
import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.helper.CrashlyticsTree import chat.rocket.android.helper.CrashlyticsTree
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
...@@ -84,6 +85,10 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -84,6 +85,10 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject @Inject
lateinit var localRepository: LocalRepository lateinit var localRepository: LocalRepository
@Inject
@field:ForMessages
lateinit var messagesPrefs: SharedPreferences
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
...@@ -107,6 +112,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -107,6 +112,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
setupFresco() setupFresco()
setupTimber() setupTimber()
if (localRepository.needOldMessagesCleanUp()) {
messagesPrefs.edit {
clear()
}
localRepository.setOldMessagesCleanedUp()
}
// TODO - remove this and all realm stuff when we got to 80% in 2.0 // TODO - remove this and all realm stuff when we got to 80% in 2.0
try { try {
if (!localRepository.hasMigrated()) { if (!localRepository.hasMigrated()) {
...@@ -286,5 +298,9 @@ private fun LocalRepository.setMigrated(migrated: Boolean) { ...@@ -286,5 +298,9 @@ private fun LocalRepository.setMigrated(migrated: Boolean) {
} }
private fun LocalRepository.hasMigrated() = getBoolean(LocalRepository.MIGRATION_FINISHED_KEY) private fun LocalRepository.hasMigrated() = getBoolean(LocalRepository.MIGRATION_FINISHED_KEY)
private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true)
private fun LocalRepository.setOldMessagesCleanedUp() = save(CLEANUP_OLD_MESSAGES_NEEDED, false)
private const val INTERNAL_TOKEN_MIGRATION_NEEDED = "INTERNAL_TOKEN_MIGRATION_NEEDED" private const val INTERNAL_TOKEN_MIGRATION_NEEDED = "INTERNAL_TOKEN_MIGRATION_NEEDED"
private const val CLEANUP_OLD_MESSAGES_NEEDED = "CLEANUP_OLD_MESSAGES_NEEDED"
\ No newline at end of file
...@@ -59,6 +59,7 @@ class LoginPresenter @Inject constructor( ...@@ -59,6 +59,7 @@ class LoginPresenter @Inject constructor(
setupConnectionInfo(currentServer) setupConnectionInfo(currentServer)
setupLoginView() setupLoginView()
setupUserRegistrationView() setupUserRegistrationView()
setupForgotPasswordView()
setupCasView() setupCasView()
setupOauthServicesView() setupOauthServicesView()
} }
...@@ -107,6 +108,8 @@ class LoginPresenter @Inject constructor( ...@@ -107,6 +108,8 @@ class LoginPresenter @Inject constructor(
fun signup() = navigator.toSignUp() fun signup() = navigator.toSignUp()
fun forgotPassword() = navigator.toForgotPassword()
private fun setupLoginView() { private fun setupLoginView() {
if (settings.isLoginFormEnabled()) { if (settings.isLoginFormEnabled()) {
view.showFormView() view.showFormView()
...@@ -127,8 +130,15 @@ class LoginPresenter @Inject constructor( ...@@ -127,8 +130,15 @@ class LoginPresenter @Inject constructor(
private fun setupUserRegistrationView() { private fun setupUserRegistrationView() {
if (settings.isRegistrationEnabledForNewUsers() && settings.isLoginFormEnabled()) { if (settings.isRegistrationEnabledForNewUsers() && settings.isLoginFormEnabled()) {
view.showSignUpView()
view.setupSignUpView() view.setupSignUpView()
view.showSignUpView()
}
}
private fun setupForgotPasswordView() {
if (settings.isPasswordResetEnabled()) {
view.setupForgotPasswordView()
view.showForgotPasswordView()
} }
} }
...@@ -207,6 +217,31 @@ class LoginPresenter @Inject constructor( ...@@ -207,6 +217,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 +334,38 @@ class LoginPresenter @Inject constructor( ...@@ -299,6 +334,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
...@@ -66,6 +65,18 @@ interface LoginView : LoadingView, MessageView { ...@@ -66,6 +65,18 @@ interface LoginView : LoadingView, MessageView {
*/ */
fun setupSignUpView() fun setupSignUpView()
/**
* Shows the forgot password view if enabled by the server settings.
*
* REMARK: We must set up the forgot password view listener [setupForgotPasswordView].
*/
fun showForgotPasswordView()
/**
* Setups the forgot password view when tapped.
*/
fun setupForgotPasswordView()
/** /**
* Hides the sign up view. * Hides the sign up view.
*/ */
...@@ -75,7 +86,7 @@ interface LoginView : LoadingView, MessageView { ...@@ -75,7 +86,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 +189,24 @@ interface LoginView : LoadingView, MessageView { ...@@ -178,6 +189,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)).
*/ */
......
...@@ -5,6 +5,7 @@ import chat.rocket.android.R ...@@ -5,6 +5,7 @@ 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.ui.LoginFragment import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.authentication.signup.ui.SignupFragment import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
...@@ -12,6 +13,7 @@ import chat.rocket.android.authentication.ui.newServerIntent ...@@ -12,6 +13,7 @@ import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.ui.changeServerIntent import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.util.extensions.addFragmentBackStack import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.util.extensions.toPreviousView
import chat.rocket.android.webview.ui.webViewIntent import chat.rocket.android.webview.ui.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity) { class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
...@@ -28,6 +30,10 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) { ...@@ -28,6 +30,10 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
} }
} }
fun toPreviousView() {
activity.toPreviousView()
}
fun toTwoFA(username: String, password: String) { fun toTwoFA(username: String, password: String) {
activity.addFragmentBackStack("TwoFAFragment", R.id.fragment_container) { activity.addFragmentBackStack("TwoFAFragment", R.id.fragment_container) {
TwoFAFragment.newInstance(username, password) TwoFAFragment.newInstance(username, password)
...@@ -40,6 +46,12 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) { ...@@ -40,6 +46,12 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
} }
} }
fun toForgotPassword() {
activity.addFragmentBackStack("ResetPasswordFragment", R.id.fragment_container) {
ResetPasswordFragment.newInstance()
}
}
fun toWebPage(url: String) { fun toWebPage(url: String) {
activity.startActivity(activity.webViewIntent(url)) activity.startActivity(activity.webViewIntent(url))
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
......
...@@ -16,7 +16,8 @@ import kotlinx.android.synthetic.main.fragment_authentication_register_username. ...@@ -16,7 +16,8 @@ import kotlinx.android.synthetic.main.fragment_authentication_register_username.
import javax.inject.Inject import javax.inject.Inject
class RegisterUsernameFragment : Fragment(), RegisterUsernameView { class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
@Inject lateinit var presenter: RegisterUsernamePresenter @Inject
lateinit var presenter: RegisterUsernamePresenter
private lateinit var userId: String private lateinit var userId: String
private lateinit var authToken: String private lateinit var authToken: String
...@@ -41,7 +42,11 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -41,7 +42,11 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
authToken = arguments?.getString(AUTH_TOKEN) ?: "" authToken = arguments?.getString(AUTH_TOKEN) ?: ""
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_authentication_register_username) override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_authentication_register_username)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
......
package chat.rocket.android.authentication.resetpassword.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class ResetPasswordFragmentModule {
@Provides
fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
\ No newline at end of file
package chat.rocket.android.authentication.resetpassword.di
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class ResetPasswordFragmentProvider {
@ContributesAndroidInjector(modules = [ResetPasswordFragmentModule::class])
abstract fun provideResetPasswordFragment(): ResetPasswordFragment
}
\ No newline at end of file
package chat.rocket.android.authentication.resetpassword.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatInvalidResponseException
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.forgotPassword
import javax.inject.Inject
class ResetPasswordPresenter @Inject constructor(
private val view: ResetPasswordView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
factory: RocketChatClientFactory,
serverInteractor: GetCurrentServerInteractor
) {
private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
fun resetPassword(email: String) {
when {
email.isBlank() -> view.alertBlankEmail()
!email.isEmail() -> view.alertInvalidEmail()
else -> launchUI(strategy) {
view.showLoading()
try {
retryIO("forgotPassword(email = $email)") {
client.forgotPassword(email)
}
navigator.toPreviousView()
view.emailSent()
} catch (exception: RocketChatException) {
if (exception is RocketChatInvalidResponseException) {
view.updateYourServerVersion()
} else {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} finally {
view.hideLoading()
}
}
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.resetpassword.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface ResetPasswordView : LoadingView, MessageView {
/**
* Alerts the user about a blank email.
*/
fun alertBlankEmail()
/**
* Alerts the user about a invalid email.
*/
fun alertInvalidEmail()
/**
* Shows a successful email sent message.
*/
fun emailSent()
/**
* Shows a message to update the server version in order to use an app feature.
*/
fun updateYourServerVersion()
}
\ No newline at end of file
package chat.rocket.android.authentication.resetpassword.ui
import DrawableHelper
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordPresenter
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView
import chat.rocket.android.util.extensions.*
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_reset_password.*
import javax.inject.Inject
class ResetPasswordFragment : Fragment(), ResetPasswordView {
@Inject
lateinit var presenter: ResetPasswordPresenter
companion object {
fun newInstance() = ResetPasswordFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_authentication_reset_password)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.apply {
text_email.requestFocus()
showKeyboard(text_email)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
setupOnClickListener()
}
override fun alertBlankEmail() {
ui {
vibrateShakeAndRequestFocusForTextEmail()
}
}
override fun alertInvalidEmail() {
ui {
vibrateShakeAndRequestFocusForTextEmail()
showMessage(R.string.msg_invalid_email)
}
}
override fun emailSent() {
showToast(R.string.msg_check_your_email_to_reset_your_password, Toast.LENGTH_LONG)
}
override fun updateYourServerVersion() {
showMessage(R.string.msg_update_app_version_in_order_to_continue)
}
override fun showLoading() {
ui {
disableUserInput()
view_loading.setVisible(true)
}
}
override fun hideLoading() {
ui {
view_loading.setVisible(false)
enableUserInput()
}
}
override fun showMessage(resId: Int) {
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() {
showMessage(getString(R.string.msg_generic_error))
}
private fun tintEditTextDrawableStart() {
ui {
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, it)
DrawableHelper.wrapDrawable(emailDrawable)
DrawableHelper.tintDrawable(emailDrawable, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_email, emailDrawable)
}
}
private fun enableUserInput() {
button_reset_password.isEnabled = true
text_email.isEnabled = true
}
private fun disableUserInput() {
button_reset_password.isEnabled = false
text_email.isEnabled = true
}
private fun vibrateShakeAndRequestFocusForTextEmail() {
vibrateSmartPhone()
text_email.shake()
text_email.requestFocus()
}
private fun setupOnClickListener() {
button_reset_password.setOnClickListener {
presenter.resetPassword(text_email.textContent)
}
}
}
\ No newline at end of file
...@@ -61,6 +61,10 @@ class ChatRoomAdapter( ...@@ -61,6 +61,10 @@ class ChatRoomAdapter(
val view = parent.inflate(R.layout.item_color_attachment) val view = parent.inflate(R.layout.item_color_attachment)
ColorAttachmentViewHolder(view, actionsListener, reactionListener) ColorAttachmentViewHolder(view, actionsListener, reactionListener)
} }
BaseViewModel.ViewType.GENERIC_FILE_ATTACHMENT -> {
val view = parent.inflate(R.layout.item_file_attachment)
GenericFileAttachmentViewHolder(view, actionsListener, reactionListener)
}
else -> { else -> {
throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}") throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}")
} }
...@@ -102,6 +106,7 @@ class ChatRoomAdapter( ...@@ -102,6 +106,7 @@ class ChatRoomAdapter(
is MessageAttachmentViewHolder -> holder.bind(dataSet[position] as MessageAttachmentViewModel) is MessageAttachmentViewHolder -> holder.bind(dataSet[position] as MessageAttachmentViewModel)
is AuthorAttachmentViewHolder -> holder.bind(dataSet[position] as AuthorAttachmentViewModel) is AuthorAttachmentViewHolder -> holder.bind(dataSet[position] as AuthorAttachmentViewModel)
is ColorAttachmentViewHolder -> holder.bind(dataSet[position] as ColorAttachmentViewModel) is ColorAttachmentViewHolder -> holder.bind(dataSet[position] as ColorAttachmentViewModel)
is GenericFileAttachmentViewHolder -> holder.bind(dataSet[position] as GenericFileAttachmentViewModel)
} }
} }
...@@ -175,15 +180,15 @@ class ChatRoomAdapter( ...@@ -175,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 -> {
......
package chat.rocket.android.chatroom.adapter
import android.content.Intent
import android.net.Uri
import android.view.View
import chat.rocket.android.chatroom.viewmodel.GenericFileAttachmentViewModel
import chat.rocket.android.util.extensions.content
import chat.rocket.android.widget.emoji.EmojiReactionListener
import chat.rocket.common.util.ifNull
import kotlinx.android.synthetic.main.item_file_attachment.view.*
class GenericFileAttachmentViewHolder(itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null)
: BaseViewHolder<GenericFileAttachmentViewModel>(itemView, listener, reactionListener) {
init {
with(itemView) {
setupActionMenu(file_attachment_container)
setupActionMenu(text_file_name)
}
}
override fun bindViews(data: GenericFileAttachmentViewModel) {
with(itemView) {
text_file_name.content = data.attachmentTitle
text_file_name.setOnClickListener {
it.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(data.attachmentUrl)))
}
}
}
}
\ No newline at end of file
...@@ -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.
* *
......
...@@ -35,6 +35,7 @@ import kotlinx.android.synthetic.main.fragment_chat_room.* ...@@ -35,6 +35,7 @@ import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.* import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.* import kotlinx.android.synthetic.main.message_composer.*
import kotlinx.android.synthetic.main.message_list.* import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject import javax.inject.Inject
...@@ -228,6 +229,19 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -228,6 +229,19 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
verticalScrollOffset.set(0) verticalScrollOffset.set(0)
} }
presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true) presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true)
toggleNoChatView(adapter.itemCount)
}
}
private fun toggleNoChatView(size: Int) {
if (size == 0){
image_chat_icon.setVisible(true)
text_chat_title.setVisible(true)
text_chat_description.setVisible(true)
}else{
image_chat_icon.setVisible(false)
text_chat_title.setVisible(false)
text_chat_description.setVisible(false)
} }
} }
...@@ -312,6 +326,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -312,6 +326,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
adapter.prependData(message) adapter.prependData(message)
recycler_view.scrollToPosition(0) recycler_view.scrollToPosition(0)
verticalScrollOffset.set(0) verticalScrollOffset.set(0)
toggleNoChatView(adapter.itemCount)
} }
} }
......
...@@ -23,7 +23,8 @@ interface BaseViewModel<out T> { ...@@ -23,7 +23,8 @@ interface BaseViewModel<out T> {
AUDIO_ATTACHMENT(5), AUDIO_ATTACHMENT(5),
MESSAGE_ATTACHMENT(6), MESSAGE_ATTACHMENT(6),
AUTHOR_ATTACHMENT(7), AUTHOR_ATTACHMENT(7),
COLOR_ATTACHMENT(8) COLOR_ATTACHMENT(8),
GENERIC_FILE_ATTACHMENT(9)
} }
} }
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.GenericFileAttachment
import chat.rocket.core.model.attachment.ImageAttachment
data class GenericFileAttachmentViewModel(
override val message: Message,
override val rawData: GenericFileAttachment,
override val messageId: String,
override val attachmentUrl: String,
override val attachmentTitle: CharSequence,
override val id: Long,
override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null,
override var isTemporary: Boolean = false
) : BaseFileAttachmentViewModel<GenericFileAttachment> {
override val viewType: Int
get() = BaseViewModel.ViewType.GENERIC_FILE_ATTACHMENT.viewType
override val layoutId: Int
get() = R.layout.item_file_attachment
}
\ No newline at end of file
...@@ -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,13 +151,14 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -152,13 +151,14 @@ 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()) {
is ImageAttachment -> context.getString(R.string.msg_preview_photo) is ImageAttachment -> context.getString(R.string.msg_preview_photo)
is VideoAttachment -> context.getString(R.string.msg_preview_video) is VideoAttachment -> context.getString(R.string.msg_preview_video)
is AudioAttachment -> context.getString(R.string.msg_preview_audio) is AudioAttachment -> context.getString(R.string.msg_preview_audio)
is GenericFileAttachment -> context.getString(R.string.msg_preview_file)
else -> attachment.text ?: "" else -> attachment.text ?: ""
} }
val content = stripMessageQuotes(message) val content = stripMessageQuotes(message)
...@@ -182,6 +182,9 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -182,6 +182,9 @@ class ViewModelMapper @Inject constructor(private val context: Context,
is AudioAttachment -> AudioAttachmentViewModel(message, attachment, message.id, is AudioAttachment -> AudioAttachmentViewModel(message, attachment, message.id,
attachmentUrl, attachmentTitle, id, getReactions(message), attachmentUrl, attachmentTitle, id, getReactions(message),
preview = message.copy(message = context.getString(R.string.msg_preview_audio))) preview = message.copy(message = context.getString(R.string.msg_preview_audio)))
is GenericFileAttachment -> GenericFileAttachmentViewModel(message, attachment,
message.id, attachmentUrl, attachmentTitle, id, getReactions(message),
preview = message.copy(message = context.getString(R.string.msg_preview_file)))
else -> null else -> null
} }
} }
...@@ -235,7 +238,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -235,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())
...@@ -261,7 +264,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -261,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()
...@@ -276,7 +279,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -276,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")
} }
} }
...@@ -302,10 +305,10 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -302,10 +305,10 @@ 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.render(message, currentUsername)
} }
} }
......
...@@ -3,6 +3,7 @@ package chat.rocket.android.dagger.module ...@@ -3,6 +3,7 @@ package chat.rocket.android.dagger.module
import chat.rocket.android.authentication.di.AuthenticationModule import chat.rocket.android.authentication.di.AuthenticationModule
import chat.rocket.android.authentication.login.di.LoginFragmentProvider import chat.rocket.android.authentication.login.di.LoginFragmentProvider
import chat.rocket.android.authentication.registerusername.di.RegisterUsernameFragmentProvider import chat.rocket.android.authentication.registerusername.di.RegisterUsernameFragmentProvider
import chat.rocket.android.authentication.resetpassword.di.ResetPasswordFragmentProvider
import chat.rocket.android.authentication.server.di.ServerFragmentProvider import chat.rocket.android.authentication.server.di.ServerFragmentProvider
import chat.rocket.android.authentication.signup.di.SignupFragmentProvider import chat.rocket.android.authentication.signup.di.SignupFragmentProvider
import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
...@@ -33,6 +34,7 @@ abstract class ActivityBuilder { ...@@ -33,6 +34,7 @@ abstract class ActivityBuilder {
ServerFragmentProvider::class, ServerFragmentProvider::class,
LoginFragmentProvider::class, LoginFragmentProvider::class,
RegisterUsernameFragmentProvider::class, RegisterUsernameFragmentProvider::class,
ResetPasswordFragmentProvider::class,
SignupFragmentProvider::class, SignupFragmentProvider::class,
TwoFAFragmentProvider::class TwoFAFragmentProvider::class
]) ])
......
...@@ -167,6 +167,7 @@ class AppModule { ...@@ -167,6 +167,7 @@ class AppModule {
} }
@Provides @Provides
@Singleton
fun provideSharedPreferences(context: Application) = fun provideSharedPreferences(context: Application) =
context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE) context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE)
...@@ -269,9 +270,9 @@ class AppModule { ...@@ -269,9 +270,9 @@ class AppModule {
} }
@Provides @Provides
@Singleton fun provideMessageParser(context: Application, configuration: SpannableConfiguration, serverInteractor: GetCurrentServerInteractor, settingsInteractor: GetSettingsInteractor): MessageParser {
fun provideMessageParser(context: Application, configuration: SpannableConfiguration): MessageParser { val url = serverInteractor.get()!!
return MessageParser(context, configuration) 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,23 +28,34 @@ import ru.noties.markwon.Markwon ...@@ -29,23 +28,34 @@ 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()
/** /**
* Render a markdown text message to Spannable. * Render markdown and other rules on message to rich text with spans.
* *
* @param message The [Message] object we're interested on rendering. * @param message The [Message] object we're interested on rendering.
* @param selfUsername This user username. * @param selfUsername This user username.
* *
* @return A Spannable with the parsed markdown. * @return A Spannable with the parsed markdown.
*/ */
fun renderMarkdown(message: Message, selfUsername: String? = null): CharSequence { fun render(message: Message, selfUsername: String? = null): CharSequence {
val text = message.message var text: String = message.message
val mentions = mutableListOf<String>()
message.mentions?.forEach {
val mention = getMention(it)
mentions.add(mention)
if (it.username != null) {
text = text.replace("@${it.username}", mention)
}
}
val builder = SpannableBuilder() val builder = SpannableBuilder()
val content = EmojiRepository.shortnameToUnicode(text, true) val content = EmojiRepository.shortnameToUnicode(text, true)
val parentNode = parser.parse(toLenientMarkdown(content)) val parentNode = parser.parse(toLenientMarkdown(content))
...@@ -53,7 +63,7 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -53,7 +63,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, mentions, selfUsername))
} }
return builder.text() return builder.text()
...@@ -66,26 +76,38 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -66,26 +76,38 @@ 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, private fun getMention(user: SimpleUser): String {
return if (settings.useRealName()) {
user.name ?: "@${user.username}"
} else {
"@${user.username}"
}
}
class MentionVisitor(
context: Context,
private val builder: SpannableBuilder, private val builder: SpannableBuilder,
private val mentions: List<SimpleUser>, private val mentions: List<String>,
private val currentUser: String?) : AbstractVisitor() { private val currentUser: String?
) : 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.toMutableList().also {
mentionsList.add("all") it.add("@all")
mentionsList.add("here") it.add("@here")
}.toList()
mentionsList.toList().forEach {
if (it != null) { mentionsList.forEach {
val mentionMe = it == currentUser || it == "all" || it == "here" val mentionMe = it == currentUser || it == "@all" || it == "@here"
var offset = text.indexOf("@$it", 0, true) var offset = text.indexOf(it, 0, true)
while (offset > -1) { while (offset > -1) {
val textColor = if (mentionMe) myselfTextColor else othersTextColor val textColor = if (mentionMe) myselfTextColor else othersTextColor
val backgroundColor = if (mentionMe) myselfBackgroundColor else othersBackgroundColor val backgroundColor = if (mentionMe) myselfBackgroundColor else othersBackgroundColor
...@@ -99,10 +121,12 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -99,10 +121,12 @@ 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 +151,7 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -127,7 +151,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 +174,14 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -150,11 +174,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"
}
} }
...@@ -7,11 +7,11 @@ interface LocalRepository { ...@@ -7,11 +7,11 @@ interface LocalRepository {
fun save(key: String, value: Int) fun save(key: String, value: Int)
fun save(key: String, value: Long) fun save(key: String, value: Long)
fun save(key: String, value: Float) fun save(key: String, value: Float)
fun get(key: String): String? fun get(key: String, defValue: String? = null): String?
fun getBoolean(key: String): Boolean fun getBoolean(key: String, defValue: Boolean = false): Boolean
fun getFloat(key: String): Float fun getFloat(key: String, defValue: Float = -1f): Float
fun getInt(key: String): Int fun getInt(key: String, defValue: Int = -1): Int
fun getLong(key: String): Long fun getLong(key: String, defValue: Long = -1L): Long
fun clear(key: String) fun clear(key: String)
fun clearAllFromServer(server: String) fun clearAllFromServer(server: String)
......
package chat.rocket.android.infrastructure package chat.rocket.android.infrastructure
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit
class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : LocalRepository { class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : LocalRepository {
override fun getBoolean(key: String) = preferences.getBoolean(key, false) override fun getBoolean(key: String, defValue: Boolean) = preferences.getBoolean(key, defValue)
override fun getFloat(key: String) = preferences.getFloat(key, -1f) override fun getFloat(key: String, defValue: Float) = preferences.getFloat(key, defValue)
override fun getInt(key: String) = preferences.getInt(key, -1) override fun getInt(key: String, defValue: Int) = preferences.getInt(key, defValue)
override fun getLong(key: String) = preferences.getLong(key, -1L) override fun getLong(key: String, defValue: Long) = preferences.getLong(key, defValue)
override fun save(key: String, value: Int) = preferences.edit().putInt(key, value).apply() override fun save(key: String, value: Int) = preferences.edit { putInt(key, value) }
override fun save(key: String, value: Float) = preferences.edit().putFloat(key, value).apply() override fun save(key: String, value: Float) = preferences.edit { putFloat(key, value) }
override fun save(key: String, value: Long) = preferences.edit().putLong(key, value).apply() override fun save(key: String, value: Long) = preferences.edit { putLong(key, value) }
override fun save(key: String, value: Boolean) = preferences.edit().putBoolean(key, value).apply() override fun save(key: String, value: Boolean) = preferences.edit { putBoolean(key, value) }
override fun save(key: String, value: String?) = preferences.edit().putString(key, value).apply() override fun save(key: String, value: String?) = preferences.edit { putString(key, value) }
override fun get(key: String): String? = preferences.getString(key, null) override fun get(key: String, defValue: String?): String? = preferences.getString(key, defValue)
override fun clear(key: String) = preferences.edit().remove(key).apply() override fun clear(key: String) = preferences.edit { remove(key) }
override fun clearAllFromServer(server: String) { override fun clearAllFromServer(server: String) {
clear(LocalRepository.KEY_PUSH_TOKEN) clear(LocalRepository.KEY_PUSH_TOKEN)
......
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
...@@ -37,21 +38,33 @@ fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> ...@@ -37,21 +38,33 @@ fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () ->
.commit() .commit()
} }
fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, fun AppCompatActivity.addFragmentBackStack(
newInstance: () -> Fragment) { tag: String,
layoutId: Int,
newInstance: () -> Fragment
) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance() val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, .setCustomAnimations(
R.anim.enter_from_left, R.anim.exit_to_right) R.anim.enter_from_right, R.anim.exit_to_left,
R.anim.enter_from_left, R.anim.exit_to_right
)
.replace(layoutId, fragment, tag) .replace(layoutId, fragment, tag)
.addToBackStack(tag) .addToBackStack(tag)
.commit() .commit()
} }
fun AppCompatActivity.toPreviousView() {
supportFragmentManager.popBackStack()
}
fun Activity.hideKeyboard() { fun Activity.hideKeyboard() {
if (currentFocus != null) { if (currentFocus != null) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(currentFocus.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN) imm.hideSoftInputFromWindow(
currentFocus.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
} }
} }
......
...@@ -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"
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
</vector>
...@@ -72,6 +72,21 @@ ...@@ -72,6 +72,21 @@
app:layout_constraintTop_toBottomOf="@+id/button_cas" app:layout_constraintTop_toBottomOf="@+id/button_cas"
tools:visibility="visible" /> tools:visibility="visible" />
<TextView
android:id="@+id/text_forgot_your_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="8dp"
android:gravity="center"
android:textColorLink="@color/colorAccent"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_new_to_rocket_chat"
tools:visibility="visible" />
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
android:layout_width="wrap_content" android:layout_width="wrap_content"
...@@ -99,7 +114,7 @@ ...@@ -99,7 +114,7 @@
android:visibility="gone" android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_new_to_rocket_chat" app:layout_constraintTop_toBottomOf="@+id/text_forgot_your_password"
tools:visibility="visible"> tools:visibility="visible">
<TextView <TextView
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".authentication.resetpassword.ui.ResetPasswordFragment">
<TextView
android:id="@+id/text_headline"
style="@style/Authentication.Headline.TextView"
android:layout_centerHorizontal="true"
android:text="@string/title_reset_password" />
<EditText
android:id="@+id/text_email"
style="@style/Authentication.EditText"
android:layout_below="@id/text_headline"
android:layout_marginTop="32dp"
android:drawableStart="@drawable/ic_email_black_24dp"
android:hint="@string/msg_email"
android:imeOptions="actionDone"
android:inputType="textEmailAddress" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorName="BallPulseIndicator"
tools:visibility="visible" />
<Button
android:id="@+id/button_reset_password"
style="@style/Authentication.Button"
android:layout_alignParentBottom="true"
android:text="@string/title_reset_password" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout" android:id="@+id/root_layout"
...@@ -11,7 +12,10 @@ ...@@ -11,7 +12,10 @@
android:id="@+id/view_loading" android:id="@+id/view_loading"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone" android:visibility="gone"
app:indicatorColor="@color/black" app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator" app:indicatorName="BallPulseIndicator"
...@@ -19,9 +23,12 @@ ...@@ -19,9 +23,12 @@
<FrameLayout <FrameLayout
android:id="@+id/message_list_container" android:id="@+id/message_list_container"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_above="@+id/layout_message_composer"> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer">
<include <include
android:id="@+id/layout_message_list" android:id="@+id/layout_message_list"
...@@ -31,11 +38,57 @@ ...@@ -31,11 +38,57 @@
</FrameLayout> </FrameLayout>
<ImageView
android:id="@+id/image_chat_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_chat_black_24dp"
android:tint="@color/icon_grey"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_chat_title"
app:layout_constraintVertical_chainStyle="packed"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/text_chat_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_no_chat_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_chat_icon"
app:layout_constraintBottom_toTopOf="@id/text_chat_description"
android:textSize="20sp"
android:layout_marginTop="24dp"
android:textStyle="bold"
android:textColor="@color/colorSecondaryText"
android:visibility="gone"
tools:visibility="visible"/>
<TextView
android:id="@+id/text_chat_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_no_chat_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_chat_title"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:layout_marginTop="16dp"
android:textAlignment="center"
android:textSize="16sp"
android:textColor="@color/colorSecondaryTextLight"
android:visibility="gone"
tools:visibility="visible"/>
<chat.rocket.android.widget.autocompletion.ui.SuggestionsView <chat.rocket.android.widget.autocompletion.ui.SuggestionsView
android:id="@+id/suggestions_view" android:id="@+id/suggestions_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@+id/layout_message_composer" app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:background="@color/suggestion_background_color" /> android:background="@color/suggestion_background_color" />
<include <include
...@@ -43,13 +96,13 @@ ...@@ -43,13 +96,13 @@
layout="@layout/message_composer" layout="@layout/message_composer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" /> app:layout_constraintBottom_toBottomOf="parent" />
<View <View
android:id="@+id/view_dim" android:id="@+id/view_dim"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_above="@+id/layout_message_composer" app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:background="@color/colorDim" android:background="@color/colorDim"
android:visibility="gone" /> android:visibility="gone" />
...@@ -58,7 +111,7 @@ ...@@ -58,7 +111,7 @@
layout="@layout/message_attachment_options" layout="@layout/message_attachment_options"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@+id/layout_message_composer" app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:layout_margin="5dp" android:layout_margin="5dp"
android:visibility="gone" /> android:visibility="gone" />
...@@ -77,4 +130,4 @@ ...@@ -77,4 +130,4 @@
tools:text="connected" tools:text="connected"
tools:visibility="visible" /> tools:visibility="visible" />
</RelativeLayout> </android.support.constraint.ConstraintLayout>
\ No newline at end of file
...@@ -53,8 +53,7 @@ ...@@ -53,8 +53,7 @@
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="@id/text_author_name" app:layout_constraintTop_toBottomOf="@id/text_author_name"
app:layout_constraintStart_toStartOf="@id/text_author_name" app:layout_constraintStart_toStartOf="@id/text_author_name"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent" />
tools:visibility="visible" />
<include <include
layout="@layout/layout_reactions" layout="@layout/layout_reactions"
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/file_attachment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding">
<TextView
android:id="@+id/text_file_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="56dp"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:drawableStart="@drawable/ic_files_24dp"
android:drawablePadding="6dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="This is a very, very, very long filename, to test how the layout will work on very very very long filenames.pdf" />
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/text_file_name"
app:layout_constraintTop_toBottomOf="@id/text_file_name" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:background="@color/default_background">
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout
android:id="@+id/composer" android:id="@+id/composer"
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
<string name="title_sign_in_your_server">Inicia sesión en tu servidor</string> <string name="title_sign_in_your_server">Inicia sesión en tu servidor</string>
<string name="title_log_in">Iniciar sesión</string> <string name="title_log_in">Iniciar sesión</string>
<string name="title_register_username">Registrar nombre de usuario</string> <string name="title_register_username">Registrar nombre de usuario</string>
// TODO: Add proper translation.
<string name="title_reset_password">Reset Password</string>
<string name="title_sign_up">Regístrate</string> <string name="title_sign_up">Regístrate</string>
<string name="title_authentication">Autenticación</string> <string name="title_authentication">Autenticación</string>
<string name="title_legal_terms">Términos legales</string> <string name="title_legal_terms">Términos legales</string>
...@@ -51,6 +53,14 @@ ...@@ -51,6 +53,14 @@
<string name="msg_avatar_url">URL del avatar</string> <string name="msg_avatar_url">URL del avatar</string>
<string name="msg_or_continue_using_social_accounts">O continuar usando cuentas sociales</string> <string name="msg_or_continue_using_social_accounts">O continuar usando cuentas sociales</string>
<string name="msg_new_user">Nuevo usuario? %1$s</string> <string name="msg_new_user">Nuevo usuario? %1$s</string>
// TODO: Add proper translation.
<string name="msg_forgot_password">Forgot password? %1$s</string>
// TODO: Add proper translation.
<string name="msg_reset">Reset</string>
// TODO: Add proper translation.
<string name="msg_check_your_email_to_reset_your_password">Email sent! Check your inbox to reset your password.</string>
// TODO: Add proper translation.
<string name="msg_invalid_email">Please type a valid e-mail</string>
<string name="msg_new_user_agreement">Al continuar estás aceptando nuestra\n%1$s y %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">Código 2FA</string> <string name="msg_2fa_code">Código 2FA</string>
<string name="msg_yesterday">Ayer</string> <string name="msg_yesterday">Ayer</string>
...@@ -78,10 +88,13 @@ ...@@ -78,10 +88,13 @@
<string name="msg_preview_video">Vídeo</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">Foto</string> <string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Fichero</string>
<string name="msg_no_messages_yet">Aún no hay mensajes</string> <string name="msg_no_messages_yet">Aún no hay mensajes</string>
<string name="msg_version">Versión %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>
// TODO: Add proper translation.
<string name="msg_update_app_version_in_order_to_continue">Out to date server version. Please contact the server admin to update the server version in order to continue.</string>
<string name="msg_ver_not_recommended"> <string name="msg_ver_not_recommended">
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> 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">
...@@ -96,6 +109,8 @@ ...@@ -96,6 +109,8 @@
<string name="msg_invalid_server_protocol">El protocolo seleccionado no es aceptado por este servidor, intente usar HTTPS</string> <string name="msg_invalid_server_protocol">El protocolo seleccionado no es aceptado por este servidor, intente usar HTTPS</string>
<string name="msg_image_saved_successfully">La imagen se ha guardado en la galería</string> <string name="msg_image_saved_successfully">La imagen se ha guardado en la galería</string>
<string name="msg_image_saved_failed">Error al guardar la imagen</string> <string name="msg_image_saved_failed">Error al guardar la imagen</string>
<string name="msg_no_chat_title">Sin mensajes de chat</string>
<string name="msg_no_chat_description">Comience a conversar para ver\nsus mensajes aquí.</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">Nombre de la sala cambiado para: %1$s por %2$s</string> <string name="message_room_name_changed">Nombre de la sala cambiado para: %1$s por %2$s</string>
......
This diff is collapsed.
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
<string name="title_sign_in_your_server">अपने सर्वर में साइन इन करें</string> <string name="title_sign_in_your_server">अपने सर्वर में साइन इन करें</string>
<string name="title_log_in">लॉग इन करें</string> <string name="title_log_in">लॉग इन करें</string>
<string name="title_register_username">रजिस्टर उपयोगकर्ता नाम</string> <string name="title_register_username">रजिस्टर उपयोगकर्ता नाम</string>
// TODO: Add proper translation.
<string name="title_reset_password">Reset password</string>
<string name="title_sign_up">साइन अप करें</string> <string name="title_sign_up">साइन अप करें</string>
<string name="title_authentication">प्रमाणीकरण</string> <string name="title_authentication">प्रमाणीकरण</string>
<string name="title_legal_terms">कानूनी शर्तें</string> <string name="title_legal_terms">कानूनी शर्तें</string>
...@@ -52,6 +54,14 @@ ...@@ -52,6 +54,14 @@
<string name="msg_avatar_url">अवतार यूआरएल</string> <string name="msg_avatar_url">अवतार यूआरएल</string>
<string name="msg_or_continue_using_social_accounts">या सामाजिक खाते का उपयोग करना जारी रखें</string> <string name="msg_or_continue_using_social_accounts">या सामाजिक खाते का उपयोग करना जारी रखें</string>
<string name="msg_new_user">नया उपयोगकर्ता? %1$s</string> <string name="msg_new_user">नया उपयोगकर्ता? %1$s</string>
// TODO: Add proper translation.
<string name="msg_forgot_password">Forgot password? %1$s</string>
// TODO: Add proper translation.
<string name="msg_reset">Reset</string>
// TODO: Add proper translation.
<string name="msg_check_your_email_to_reset_your_password">Email sent! Check your inbox to reset your password.</string>
// TODO: Add proper translation.
<string name="msg_invalid_email">Please type a valid e-mail</string>
<string name="msg_new_user_agreement">आगे बढ़कर आप हमारे %1$s और %2$s से सहमत हो रहे हैं</string> <string name="msg_new_user_agreement">आगे बढ़कर आप हमारे %1$s और %2$s से सहमत हो रहे हैं</string>
<string name="msg_2fa_code">कोड 2FA</string> <string name="msg_2fa_code">कोड 2FA</string>
<string name="msg_yesterday">कल</string> <string name="msg_yesterday">कल</string>
...@@ -78,10 +88,13 @@ ...@@ -78,10 +88,13 @@
<string name="msg_preview_video">वीडियो</string> <string name="msg_preview_video">वीडियो</string>
<string name="msg_preview_audio">ऑडियो</string> <string name="msg_preview_audio">ऑडियो</string>
<string name="msg_preview_photo">तस्वीरें</string> <string name="msg_preview_photo">तस्वीरें</string>
<string name="msg_preview_file">File</string>
<string name="msg_unread_messages">अपठित संदेश</string> <string name="msg_unread_messages">अपठित संदेश</string>
<string name="msg_no_messages_yet">अभी तक कोई पोस्ट नहीं</string> <string name="msg_no_messages_yet">अभी तक कोई पोस्ट नहीं</string>
<string name="msg_version">वर्शन %1$s</string> <string name="msg_version">वर्शन %1$s</string>
<string name="msg_build">बिल्ड %1$d</string> <string name="msg_build">बिल्ड %1$d</string>
// TODO: Add proper translation.
<string name="msg_update_app_version_in_order_to_continue">Out to date server version. Please contact the server admin to update the server version in order to continue.</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">
ऐसा लगता है कि आपका सर्वर संस्करण अनुशंसित संस्करण %1$s के नीचे है।\nआप अभी भी लॉगिन कर सकते हैं लेकिन आप अप्रत्याशित व्यवहार का अनुभव कर सकते हैं ऐसा लगता है कि आपका सर्वर संस्करण अनुशंसित संस्करण %1$s के नीचे है।\nआप अभी भी लॉगिन कर सकते हैं लेकिन आप अप्रत्याशित व्यवहार का अनुभव कर सकते हैं
...@@ -97,6 +110,8 @@ ...@@ -97,6 +110,8 @@
<string name="msg_invalid_server_protocol">चयनित प्रोटोकॉल इस सर्वर द्वारा स्वीकार नहीं किया गया है, HTTPS का उपयोग करने का प्रयास करें</string> <string name="msg_invalid_server_protocol">चयनित प्रोटोकॉल इस सर्वर द्वारा स्वीकार नहीं किया गया है, HTTPS का उपयोग करने का प्रयास करें</string>
<string name="msg_image_saved_successfully">छवि गैलरी में सहेजा गया है</string> <string name="msg_image_saved_successfully">छवि गैलरी में सहेजा गया है</string>
<string name="msg_image_saved_failed">छवि को सहेजने में विफल</string> <string name="msg_image_saved_failed">छवि को सहेजने में विफल</string>
<string name="msg_no_chat_title">कोई चैट संदेश नहीं</string>
<string name="msg_no_chat_description">यहां अपने संदेश देखने के लिए\nबातचीत शुरू करें।</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string> <string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string>
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<string name="title_sign_in_your_server">Faça login no seu servidor</string> <string name="title_sign_in_your_server">Faça login no seu servidor</string>
<string name="title_log_in">Entrar</string> <string name="title_log_in">Entrar</string>
<string name="title_register_username">Registre o nome de usuário</string> <string name="title_register_username">Registre o nome de usuário</string>
<string name="title_reset_password">Redefinir senha</string>
<string name="title_sign_up">Inscreva-se</string> <string name="title_sign_up">Inscreva-se</string>
<string name="title_authentication">Autenticação</string> <string name="title_authentication">Autenticação</string>
<string name="title_legal_terms">Termos Legais</string> <string name="title_legal_terms">Termos Legais</string>
...@@ -52,6 +53,10 @@ ...@@ -52,6 +53,10 @@
<string name="msg_avatar_url">URL do avatar</string> <string name="msg_avatar_url">URL do avatar</string>
<string name="msg_or_continue_using_social_accounts">Ou continue através de contas sociais</string> <string name="msg_or_continue_using_social_accounts">Ou continue através de contas sociais</string>
<string name="msg_new_user">Novo usuário? %1$s</string> <string name="msg_new_user">Novo usuário? %1$s</string>
<string name="msg_forgot_password">Esqueceu a senha? %1$s</string>
<string name="msg_reset">Redefinir</string>
<string name="msg_check_your_email_to_reset_your_password">Email enviado! Verifique sua caixa de entrada para redefinir sua senha.</string>
<string name="msg_invalid_email">Por favor informe um e-mail válido</string>
<string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string> <string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string>
<string name="msg_2fa_code">Código 2FA</string> <string name="msg_2fa_code">Código 2FA</string>
<string name="msg_yesterday">ontem</string> <string name="msg_yesterday">ontem</string>
...@@ -79,16 +84,20 @@ ...@@ -79,16 +84,20 @@
<string name="msg_preview_video">Vídeo</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">Foto</string> <string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Arquivo</string>
<string name="msg_no_messages_yet">Nenhuma mensagem ainda</string> <string name="msg_no_messages_yet">Nenhuma mensagem ainda</string>
<string name="msg_version">Versão %1$s</string> <string name="msg_version">Versão %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_update_app_version_in_order_to_continue">Versão do servidor desatualizada. Por favor, entre em contato com o administrador do sistema para continuar.</string>
<string name="msg_ver_not_recommended"> <string name="msg_ver_not_recommended">
Parece que a versão do seu servidor está abaixo da recomendada %1$s.\nVocê ainda assim pode logar e continuar mas podem ocorrer alguns problemas inesperados. Parece que a versão do seu servidor está abaixo da recomendada %1$s.\nVocê ainda assim pode logar e continuar mas podem ocorrer alguns problemas inesperados.
</string> </string>
<string name="msg_ver_not_minimum"> <string name="msg_ver_not_minimum">
Parece que a versão do seu servidor está abaixo da mínima requerida %1$s.\nPor favor, atualize seus servidores antes de continuar! Parece que a versão do seu servidor está abaixo da mínima requerida %1$s.\nPor favor, atualize seus servidores antes de continuar!
</string> </string>
<string name="msg_no_chat_title">Nenhuma mensagem de chat</string>
<string name="msg_no_chat_description">Comece a conversar para ver suas\nmensagens aqui.</string>
<string name="msg_proceed">CONTINUAR</string> <string name="msg_proceed">CONTINUAR</string>
<string name="msg_cancel">CANCELAR</string> <string name="msg_cancel">CANCELAR</string>
<string name="msg_warning">AVISO</string> <string name="msg_warning">AVISO</string>
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
<!-- Text colors --> <!-- Text colors -->
<color name="colorPrimaryText">#DE000000</color> <color name="colorPrimaryText">#DE000000</color>
<color name="colorSecondaryText">#787878</color> <color name="colorSecondaryText">#787878</color>
<color name="colorSecondaryTextLight">#c1c1c1</color>
<!-- User status colors --> <!-- User status colors -->
<color name="colorUserStatusOnline">#2FE1A8</color> <color name="colorUserStatusOnline">#2FE1A8</color>
...@@ -44,4 +45,8 @@ ...@@ -44,4 +45,8 @@
<!-- Suggestions --> <!-- Suggestions -->
<color name="suggestion_background_color">@android:color/white</color> <color name="suggestion_background_color">@android:color/white</color>
<color name="icon_grey">#AFADAF</color>
<!-- Default Background Color -->
<color name="default_background">#FAFAFA</color>
</resources> </resources>
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<string name="title_sign_in_your_server">Sign in your server</string> <string name="title_sign_in_your_server">Sign in your server</string>
<string name="title_log_in">Log in</string> <string name="title_log_in">Log in</string>
<string name="title_register_username">Register username</string> <string name="title_register_username">Register username</string>
<string name="title_reset_password">Reset password</string>
<string name="title_sign_up">Sign up</string> <string name="title_sign_up">Sign up</string>
<string name="title_authentication">Authentication</string> <string name="title_authentication">Authentication</string>
<string name="title_legal_terms">Legal Terms</string> <string name="title_legal_terms">Legal Terms</string>
...@@ -53,6 +54,10 @@ ...@@ -53,6 +54,10 @@
<string name="msg_avatar_url">avatar URL</string> <string name="msg_avatar_url">avatar URL</string>
<string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string> <string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string>
<string name="msg_new_user">New user? %1$s</string> <string name="msg_new_user">New user? %1$s</string>
<string name="msg_forgot_password">Forgot password? %1$s</string>
<string name="msg_reset">Reset</string>
<string name="msg_check_your_email_to_reset_your_password">Email sent! Check your inbox to reset your password.</string>
<string name="msg_invalid_email">Please type a valid e-mail</string>
<string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string> <string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string>
<string name="msg_2fa_code">2FA Code</string> <string name="msg_2fa_code">2FA Code</string>
<string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string> <string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string>
...@@ -81,15 +86,19 @@ ...@@ -81,15 +86,19 @@
<string name="msg_preview_video">Video</string> <string name="msg_preview_video">Video</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_no_messages_yet">No messages yet</string> <string name="msg_no_messages_yet">No messages yet</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_update_app_version_in_order_to_continue">Out to date server version. Please contact the server admin to update the server version in order to continue.</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> Looks like your server version is below the recommended version %1$s.\nYou can still login but you may experience unexpected behaviors.</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! Looks like your server version is below the minimum required version %1$s.\nPlease upgrade your server to login!
</string> </string>
<string name="msg_no_chat_title">No chat messages</string>
<string name="msg_no_chat_description">Start conversing to see your\nmessages here.</string>
<string name="msg_proceed">PROCEED</string> <string name="msg_proceed">PROCEED</string>
<string name="msg_cancel">CANCEL</string> <string name="msg_cancel">CANCEL</string>
<string name="msg_warning">WARNING</string> <string name="msg_warning">WARNING</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