Commit 16a42823 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Merge branch 'develop-2.x' into new/reactions

parents fefae728 61cb7b9e
......@@ -41,7 +41,12 @@
android:theme="@style/AppTheme" />
<activity
android:name=".webview.WebViewActivity"
android:name=".webview.ui.WebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
<activity
android:name=".webview.cas.ui.CasWebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
......
......@@ -4,17 +4,20 @@ import chat.rocket.android.authentication.domain.model.TokenModel
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.isEmailValid
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.model.Token
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.*
import kotlinx.coroutines.experimental.delay
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class LoginPresenter @Inject constructor(private val view: LoginView,
......@@ -28,51 +31,69 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
// TODO - we should validate the current server when opening the app, and have a nonnull get()
private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
fun setup() {
fun setupView() {
val server = serverInteractor.get()
if (server == null) {
navigator.toServerScreen()
return
}
val settings = settingsInteractor.get(server)
if (settings == null) {
navigator.toServerScreen()
return
if (settings.isLoginFormEnabled()) {
view.showFormView()
view.setupLoginButtonListener()
view.setupGlobalListener()
} else {
view.hideFormView()
}
if (settings.isRegistrationEnabledForNewUsers()) {
view.showSignUpView()
view.setupSignUpView()
}
view.showSignUpView(settings.registrationEnabled())
if (settings.isCasAuthenticationEnabled()) {
val token = generateRandomString(17)
view.setupCasButtonListener(UrlHelper.getCasUrl(settings.casLoginUrl(), server, token), token)
view.showCasButton()
}
var hasSocial = false
if (settings.facebookEnabled()) {
var totalSocialAccountsEnabled = 0
if (settings.isFacebookAuthenticationEnabled()) {
view.enableLoginByFacebook()
hasSocial = true
totalSocialAccountsEnabled++
}
if (settings.githubEnabled()) {
if (settings.isGithubAuthenticationEnabled()) {
view.enableLoginByGithub()
hasSocial = true
totalSocialAccountsEnabled++
}
if (settings.googleEnabled()) {
if (settings.isGoogleAuthenticationEnabled()) {
view.enableLoginByGoogle()
hasSocial = true
totalSocialAccountsEnabled++
}
if (settings.linkedinEnabled()) {
if (settings.isLinkedinAuthenticationEnabled()) {
view.enableLoginByLinkedin()
hasSocial = true
totalSocialAccountsEnabled++
}
if (settings.meteorEnabled()) {
if (settings.isMeteorAuthenticationEnabled()) {
view.enableLoginByMeteor()
hasSocial = true
totalSocialAccountsEnabled++
}
if (settings.twitterEnabled()) {
if (settings.isTwitterAuthenticationEnabled()) {
view.enableLoginByTwitter()
hasSocial = true
totalSocialAccountsEnabled++
}
if (settings.gitlabEnabled()) {
if (settings.isGitlabAuthenticationEnabled()) {
view.enableLoginByGitlab()
hasSocial = true
totalSocialAccountsEnabled++
}
if (totalSocialAccountsEnabled > 0) {
view.showOauthView()
if (totalSocialAccountsEnabled > 3) {
view.setupFabListener()
}
}
view.showOauthView(hasSocial)
}
fun authenticate(usernameOrEmail: String, password: String) {
......@@ -90,34 +111,23 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
else -> {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.disableUserInput()
view.showLoading()
try {
var token: Token? = null
if (usernameOrEmail.isEmailValid()) {
token = client.loginWithEmail(usernameOrEmail, password)
val token = if (usernameOrEmail.isEmailValid()) {
client.loginWithEmail(usernameOrEmail, password)
} else {
val settings = settingsInteractor.get(server)
if (settings != null) {
token = if (settings.ldapEnabled()) {
client.loginWithLdap(usernameOrEmail, password)
} else {
client.login(usernameOrEmail, password)
}
if (settings.isLdapAuthenticationEnabled()) {
client.loginWithLdap(usernameOrEmail, password)
} else {
navigator.toServerScreen()
client.login(usernameOrEmail, password)
}
}
if (token != null) {
val me = client.me()
multiServerRepository.save(server, TokenModel(token.userId, token.authToken))
localRepository.save(LocalRepository.USERNAME_KEY, me.username)
registerPushToken()
navigator.toChatList()
} else {
view.showGenericErrorMessage()
}
saveToken(server, TokenModel(token.userId, token.authToken), client.me().username)
registerPushToken()
navigator.toChatList()
} catch (exception: RocketChatException) {
when (exception) {
is RocketChatTwoFactorException -> {
......@@ -133,6 +143,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
}
} finally {
view.hideLoading()
view.enableUserInput()
}
} else {
view.showNoInternetConnection()
......@@ -142,8 +153,46 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
}
}
fun authenticateWithCas(casToken: String) {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.disableUserInput()
view.showLoading()
try {
val server = serverInteractor.get()
if (server != null) {
delay(3, TimeUnit.SECONDS)
val token = client.loginWithCas(casToken)
saveToken(server, TokenModel(token.userId, token.authToken), client.me().username)
registerPushToken()
navigator.toChatList()
} else {
navigator.toServerScreen()
}
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
view.enableUserInput()
}
} else {
view.showNoInternetConnection()
}
}
}
fun signup() = navigator.toSignUp()
private suspend fun saveToken(server: String, tokenModel: TokenModel, username: String?) {
multiServerRepository.save(server, tokenModel)
localRepository.save(LocalRepository.USERNAME_KEY, username)
registerPushToken()
}
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it)
......
......@@ -7,62 +7,135 @@ import chat.rocket.android.core.behaviours.MessageView
interface LoginView : LoadingView, MessageView, InternetView {
/**
* Shows the oauth view if the server settings allow the login via social accounts.
* Shows the form view (i.e the username/email and password fields) if it is enabled by the server settings.
*
* REMARK: we must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* REMARK: We must set up the login button listener [setupLoginButtonListener].
* Remember to enable [enableUserInput] or disable [disableUserInput] the view interaction for the user when submitting the form.
*/
fun showFormView()
/**
* Hides the form view.
*/
fun hideFormView()
/**
* Setups the login button when tapped.
*/
fun setupLoginButtonListener()
/**
* Enables the view interactions for the user.
*/
fun enableUserInput()
/**
* Disables the view interactions for the user.
*/
fun disableUserInput()
/**
* Shows the CAS button if the sign in/sign out via CAS protocol is enabled by the server settings.
*
* REMARK: We must set up the CAS button listener [setupCasButtonListener].
*/
fun showCasButton()
/**
* Hides the CAS button.
*/
fun hideCasButton()
/**
* Setups the CAS button when tapped.
*
* @param casUrl The CAS URL to login/sign up with.
* @param casToken The requested Token sent to the CAS server.
*/
fun setupCasButtonListener(casUrl: String, casToken: String)
/**
* Shows the sign up view if the new users registration is enabled by the server settings.
*
* REMARK: We must set up the sign up view listener [setupSignUpView].
*/
fun showSignUpView()
/**
* Setups the sign up view when tapped.
*/
fun setupSignUpView()
/**
* Hides the sign up view.
*/
fun hideSignUpView()
/**
* Shows the oauth view if the login via social accounts is enabled by the server settings.
*
* REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter] or [enableLoginByGitlab]) 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).
*
* @param value True to show the oauth view, false otherwise.
*/
fun showOauthView(value: Boolean)
fun showOauthView()
/**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
* Hides the oauth view.
*/
fun setupFabListener()
fun hideOauthView()
/**
* Shows the login button.
*/
fun showLoginButton()
/**
* Hides the login button.
*/
fun hideLoginButton()
/**
* Shows the login by Facebook view.
* Shows the "login by Facebook view if it is enabled by the server settings.
*/
fun enableLoginByFacebook()
/**
* Shows the login by Github view.
* Shows the "login by Github" view if it is enabled by the server settings.
*/
fun enableLoginByGithub()
/**
* Shows the login by Google view.
* Shows the "login by Google" view if it is enabled by the server settings.
*/
fun enableLoginByGoogle()
/**
* Shows the login by Linkedin view.
* Shows the "login by Linkedin" view if it is enabled by the server settings.
*/
fun enableLoginByLinkedin()
/**
* Shows the login by Meteor view.
* Shows the "login by Meteor" view if it is enabled by the server settings.
*/
fun enableLoginByMeteor()
/**
* Shows the login by Twitter view.
* Shows the "login by Twitter" view if it is enabled by the server settings.
*/
fun enableLoginByTwitter()
/**
* Shows the login by Gitlab view.
* Shows the "login by Gitlab" view if it is enabled by the server settings.
*/
fun enableLoginByGitlab()
/**
* Shows the sign up view if the server settings allow the new users registration.
*
* @param value True to show the sign up view, false otherwise.
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
*/
fun showSignUpView(value: Boolean)
fun setupFabListener()
fun setupGlobalListener()
/**
* Alerts the user about a wrong inputted username or email.
......
......@@ -9,7 +9,7 @@ import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.webview.webViewIntent
import chat.rocket.android.webview.ui.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity, internal val context: Context) {
......@@ -37,14 +37,11 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity, int
}
fun toChatList() {
val chatList = Intent(activity, MainActivity::class.java).apply {
//TODO any parameter to pass
}
activity.startActivity(chatList)
activity.startActivity(Intent(activity, MainActivity::class.java))
activity.finish()
}
fun toServerScreen() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
}
\ No newline at end of file
......@@ -17,16 +17,14 @@ class ServerPresenter @Inject constructor(private val view: ServerView,
private val refreshSettingsInteractor: RefreshSettingsInteractor) {
fun connect(server: String) {
if (!UrlHelper.isValidUrl(server)) {
view.showInvalidServerUrl()
view.showInvalidServerUrlMessage()
} else {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
try {
refreshSettingsInteractor.refresh(server)
serverInteractor.save(server)
navigator.toLogin()
} catch (ex: Exception) {
ex.message?.let {
......
......@@ -7,7 +7,7 @@ import chat.rocket.android.core.behaviours.MessageView
interface ServerView : LoadingView, MessageView, InternetView {
/**
* Notifies the user about an invalid inputted server URL.
* Shows an invalid server URL message.
*/
fun showInvalidServerUrl()
fun showInvalidServerUrlMessage()
}
\ No newline at end of file
......@@ -30,7 +30,8 @@ class ServerFragment : Fragment(), ServerView {
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_authentication_server)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
container?.inflate(R.layout.fragment_authentication_server)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
......@@ -43,7 +44,7 @@ class ServerFragment : Fragment(), ServerView {
relative_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
override fun showInvalidServerUrl() = showMessage(getString(R.string.msg_invalid_server_url))
override fun showInvalidServerUrlMessage() = showMessage(getString(R.string.msg_invalid_server_url))
override fun showLoading() {
enableUserInput(false)
......
package chat.rocket.android.authentication.signup.ui
import DrawableHelper
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
......@@ -11,19 +10,15 @@ import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.*
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import javax.inject.Inject
class SignupFragment : Fragment(), SignupView {
@Inject lateinit var presenter: SignupPresenter
@Inject lateinit var appContext: Context
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
......@@ -68,26 +63,26 @@ class SignupFragment : Fragment(), SignupView {
}
override fun alertBlankName() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_name)
vibrateSmartPhone()
text_name.shake()
text_name.requestFocus()
}
override fun alertBlankUsername() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_username)
vibrateSmartPhone()
text_username.shake()
text_username.requestFocus()
}
override fun alertEmptyPassword() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_password)
vibrateSmartPhone()
text_password.shake()
text_password.requestFocus()
}
override fun alertBlankEmail() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_email)
vibrateSmartPhone()
text_email.shake()
text_email.requestFocus()
}
......@@ -114,15 +109,17 @@ class SignupFragment : Fragment(), SignupView {
}
private fun tintEditTextDrawableStart() {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, appContext)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, appContext)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, appContext)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, appContext)
val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, appContext, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_name, text_username, text_password, text_email), drawables)
activity?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, this)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, this)
val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_name, text_username, text_password, text_email), drawables)
}
}
private fun setUpNewUserAgreementListener() {
......
......@@ -12,10 +12,7 @@ import android.view.inputmethod.InputMethodManager
import chat.rocket.android.R
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.*
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import javax.inject.Inject
......@@ -65,10 +62,8 @@ class TwoFAFragment : Fragment(), TwoFAView {
}
override fun alertBlankTwoFactorAuthenticationCode() {
activity?.let {
AnimationHelper.vibrateSmartPhone(it)
AnimationHelper.shakeView(text_two_factor_auth)
}
vibrateSmartPhone()
text_two_factor_auth.shake()
}
override fun alertInvalidTwoFactorAuthenticationCode() = showMessage(getString(R.string.msg_invalid_2fa_code))
......
......@@ -9,6 +9,7 @@ import chat.rocket.android.chatroom.viewmodel.*
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.widget.emoji.EmojiReactionListener
import chat.rocket.core.model.Message
import chat.rocket.core.model.isSystemMessage
import timber.log.Timber
import java.security.InvalidParameterException
......@@ -105,12 +106,18 @@ class ChatRoomAdapter(
}
fun prependData(dataSet: List<BaseViewModel<*>>) {
this.dataSet.addAll(0, dataSet)
notifyItemRangeInserted(0, dataSet.size)
val item = dataSet.firstOrNull { newItem ->
this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } > -1
}
if (item == null) {
this.dataSet.addAll(0, dataSet)
notifyItemRangeInserted(0, dataSet.size)
}
}
fun updateItem(message: BaseViewModel<*>) {
var index = dataSet.indexOfLast { it.messageId == message.messageId }
val indexOfFirst = dataSet.indexOfFirst { it.messageId == message.messageId }
Timber.d("index: $index")
if (index > -1) {
message.nextDownStreamMessage = dataSet[index].nextDownStreamMessage
......@@ -120,6 +127,11 @@ class ChatRoomAdapter(
dataSet[index].nextDownStreamMessage!!.reactions = message.reactions
notifyItemChanged(--index)
}
// Delete message only if current is a system message update, i.e.: Message Removed
if (message.message.isSystemMessage() && indexOfFirst > -1 && indexOfFirst != index) {
dataSet.removeAt(indexOfFirst)
notifyItemRemoved(indexOfFirst)
}
}
}
......
......@@ -22,6 +22,9 @@ class MessageViewHolder(
override fun bindViews(data: MessageViewModel) {
with(itemView) {
if (data.isFirstUnread) new_messages_notif.visibility = View.VISIBLE
else new_messages_notif.visibility = View.GONE
text_message_time.text = data.time
text_sender.text = data.senderName
text_content.text = data.content
......
......@@ -20,14 +20,16 @@ import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import javax.inject.Inject
import timber.log.Timber
fun Context.chatRoomIntent(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean): Intent {
fun Context.chatRoomIntent(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean, chatRoomLastSeen: Long): Intent {
return Intent(this, ChatRoomActivity::class.java).apply {
putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
putExtra(INTENT_CHAT_ROOM_NAME, chatRoomName)
putExtra(INTENT_CHAT_ROOM_TYPE, chatRoomType)
putExtra(INTENT_IS_CHAT_ROOM_READ_ONLY, isChatRoomReadOnly)
putExtra(INTENT_CHAT_ROOM_LAST_SEEN, chatRoomLastSeen)
}
}
......@@ -35,6 +37,7 @@ private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
private const val INTENT_CHAT_ROOM_NAME = "chat_room_name"
private const val INTENT_CHAT_ROOM_TYPE = "chat_room_type"
private const val INTENT_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
private const val INTENT_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen"
class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
......@@ -47,6 +50,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
private lateinit var chatRoomName: String
private lateinit var chatRoomType: String
private var isChatRoomReadOnly: Boolean = false
private var chatRoomLastSeen: Long = -1L
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
......@@ -70,8 +74,10 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
setupToolbar()
chatRoomLastSeen = intent.getLongExtra(INTENT_CHAT_ROOM_LAST_SEEN, -1)
addFragment("ChatRoomFragment", R.id.fragment_container) {
newInstance(chatRoomId, chatRoomName, chatRoomType, isChatRoomReadOnly)
newInstance(chatRoomId, chatRoomName, chatRoomType, isChatRoomReadOnly, chatRoomLastSeen)
}
}
......
......@@ -19,28 +19,32 @@ import chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.util.extensions.*
import chat.rocket.android.widget.emoji.*
import chat.rocket.core.internal.realtime.State
import chat.rocket.core.model.Message
import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.item_chat.*
import kotlinx.android.synthetic.main.message_composer.*
import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber
import javax.inject.Inject
fun newInstance(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean): Fragment {
fun newInstance(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean, chatRoomLastSeen: Long): Fragment {
return ChatRoomFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
putString(BUNDLE_CHAT_ROOM_NAME, chatRoomName)
putString(BUNDLE_CHAT_ROOM_TYPE, chatRoomType)
putBoolean(BUNDLE_IS_CHAT_ROOM_READ_ONLY, isChatRoomReadOnly)
putLong(BUNDLE_CHAT_ROOM_LAST_SEEN, chatRoomLastSeen)
}
}
}
......@@ -50,18 +54,18 @@ private const val BUNDLE_CHAT_ROOM_NAME = "chat_room_name"
private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
private const val REQUEST_CODE_FOR_PERFORM_SAF = 42
private const val BUNDLE_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen"
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener {
@Inject lateinit var presenter: ChatRoomPresenter
@Inject lateinit var parser: MessageParser
private lateinit var adapter: ChatRoomAdapter
private lateinit var chatRoomId: String
private lateinit var chatRoomName: String
private lateinit var chatRoomType: String
private lateinit var emojiKeyboardPopup: EmojiKeyboardPopup
private var isChatRoomReadOnly: Boolean = false
private var chatRoomLastSeen: Long = -1
private lateinit var actionSnackbar: ActionSnackbar
private var citation: String? = null
private var editingMessageId: String? = null
......@@ -86,6 +90,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
chatRoomName = bundle.getString(BUNDLE_CHAT_ROOM_NAME)
chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE)
isChatRoomReadOnly = bundle.getBoolean(BUNDLE_IS_CHAT_ROOM_READ_ONLY)
chatRoomLastSeen = bundle.getLong(BUNDLE_CHAT_ROOM_LAST_SEEN)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
......@@ -149,6 +154,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun showMessages(dataSet: List<BaseViewModel<*>>) {
// track the message sent immediately after the current message
var prevMessageViewModel : MessageViewModel? = null
// Loop over received messages to determine first unread
for (i in dataSet.indices) {
val msgModel = dataSet[i]
if (msgModel is MessageViewModel){
val msg = msgModel.rawData
if (msg.timestamp < chatRoomLastSeen) {
// This message was sent before the last seen of the room. Hence, it was seen.
// if there is a message after (below) this, mark it firstUnread.
if (prevMessageViewModel != null) {
prevMessageViewModel.isFirstUnread = true
}
break
}
prevMessageViewModel = msgModel
}
}
activity?.apply {
if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, presenter,
......@@ -213,6 +239,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun dispatchUpdateMessage(index: Int, message: List<BaseViewModel<*>>) {
adapter.updateItem(message.last())
if (message.size > 1) {
adapter.prependData(listOf(message.first()))
}
}
override fun dispatchDeleteMessage(msgId: String) {
......
......@@ -13,7 +13,8 @@ data class MessageViewModel(
override val content: CharSequence,
override val isPinned: Boolean,
override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null
override var nextDownStreamMessage: BaseViewModel<*>? = null,
var isFirstUnread: Boolean
) : BaseMessageViewModel<Message> {
override val viewType: Int
get() = BaseViewModel.ViewType.MESSAGE.viewType
......
......@@ -140,16 +140,19 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val quoteUrl = HttpUrl.parse(url.url)
val serverUrl = HttpUrl.parse(baseUrl)
if (quoteUrl != null && serverUrl != null) {
quote = makeQuote(quoteUrl, serverUrl)
quote = makeQuote(quoteUrl, serverUrl)?.let {
getMessageWithoutQuoteMarkdown(it)
}
}
}
}
}
val content = getContent(context, message, quote)
MessageViewModel(message = message, rawData = message, messageId = message.id,
avatar = avatar!!, time = time, senderName = sender,
content = content, isPinned = message.pinned, reactions = getReactions(message))
val content = getContent(context, getMessageWithoutQuoteMarkdown(message), quote)
MessageViewModel(message = getMessageWithoutQuoteMarkdown(message), rawData = message,
messageId = message.id, avatar = avatar!!, time = time, senderName = sender,
content = content, isPinned = message.pinned, reactions = getReactions(message),
isFirstUnread = false)
}
private fun getReactions(message: Message): List<ReactionViewModel> {
......@@ -171,6 +174,13 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return reactions ?: emptyList()
}
private fun getMessageWithoutQuoteMarkdown(message: Message): Message {
val baseUrl = settings.baseUrl()
return message.copy(
message = message.message.replace("\\[\\s\\]\\($baseUrl.*\\)".toRegex(), "").trim()
)
}
private fun getSenderName(message: Message): CharSequence {
if (!message.senderAlias.isNullOrEmpty()) {
return message.senderAlias!!
......@@ -215,7 +225,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
var quoteViewModel: MessageViewModel? = null
if (quote != null) {
val quoteMessage: Message = quote
quoteViewModel = map(quoteMessage).first { it is MessageViewModel } as MessageViewModel
quoteViewModel = mapMessage(quoteMessage)
}
return parser.renderMarkdown(message.message, quoteViewModel, currentUsername)
}
......
......@@ -7,8 +7,8 @@ import chat.rocket.android.main.ui.MainActivity
class ChatRoomsNavigator(private val activity: MainActivity, private val context: Context) {
fun toChatRoom(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean) {
activity.startActivity(context.chatRoomIntent(chatRoomId, chatRoomName, chatRoomType, isChatRoomReadOnly))
fun toChatRoom(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean, chatRoomLastSeen: Long) {
activity.startActivity(context.chatRoomIntent(chatRoomId, chatRoomName, chatRoomType, isChatRoomReadOnly, chatRoomLastSeen))
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
}
}
\ No newline at end of file
......@@ -68,7 +68,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
navigator.toChatRoom(chatRoom.id, roomName,
chatRoom.type.toString(), chatRoom.readonly ?: false)
chatRoom.type.toString(), chatRoom.readonly ?: false, chatRoom.lastSeen ?: -1)
}
/**
......
package chat.rocket.android.helper
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.View
object AnimationHelper {
/**
* Shakes a view.
*/
fun shakeView(viewToShake: View, x: Float = 2F, num: Int = 0) {
if (num == 6) {
viewToShake.translationX = 0.toFloat()
return
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(ObjectAnimator.ofFloat(viewToShake, "translationX", dp(viewToShake.context, x)))
animatorSet.duration = 50
animatorSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
shakeView(viewToShake, if (num == 5) 0.toFloat() else -x, num + 1)
}
})
animatorSet.start()
}
/**
* Vibrates the smart phone.
*/
fun vibrateSmartPhone(context: Context) {
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(200)
}
}
private fun dp(context: Context, value: Float): Float {
val density = context.resources.displayMetrics.density
val result = Math.ceil(density.times(value.toDouble()))
return result.toFloat()
}
}
\ No newline at end of file
......@@ -59,6 +59,7 @@ class MessageParser @Inject constructor(val context: Application, private val co
parentNode.appendChild(quoteNode)
quoteNode.accept(QuoteMessageSenderVisitor(context, configuration, builder, senderName.length))
quoteNode = parser.parse("> ${toLenientMarkdown(quote.rawData.message)}")
quoteNode.accept(EmojiVisitor(builder))
quoteNode.accept(QuoteMessageBodyVisitor(context, configuration, builder))
}
parentNode.accept(LinkVisitor(builder))
......
......@@ -13,6 +13,17 @@ object UrlHelper {
*/
fun getAvatarUrl(serverUrl: String, avatarName: String): String = removeTrailingSlash(serverUrl) + "/avatar/" + avatarName
/**
* Returns the CAS URL.
*
* @param casLoginUrl The CAS login URL from the server settings.
* @param serverUrl The server URL.
* @param token The token to be send to the CAS server.
* @return The avatar URL.
*/
fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String =
removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token
/**
* Returns the server's Terms of Service URL.
*
......
......@@ -8,7 +8,7 @@ import chat.rocket.core.model.Value
import javax.inject.Inject
class MemberViewModelMapper @Inject constructor(serverInteractor: GetCurrentServerInteractor, getSettingsInteractor: GetSettingsInteractor) {
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)!!
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)
private val baseUrl = settings.baseUrl()
fun mapToViewModelList(memberList: List<User>): List<MemberViewModel> {
......
......@@ -38,8 +38,8 @@ class ProfilePresenter @Inject constructor (private val view: ProfileView,
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
......
......@@ -11,13 +11,17 @@ class RefreshSettingsInteractor @Inject constructor(private val factory: RocketC
private val repository: SettingsRepository) {
private var settingsFilter = arrayOf(
LDAP_ENABLE, CAS_ENABLE, CAS_LOGIN_URL,
ACCOUNT_REGISTRATION, ACCOUNT_LOGIN_FORM, ACCOUNT_PASSWORD_RESET, ACCOUNT_CUSTOM_FIELDS,
ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR,
ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, ACCOUNT_GITLAB,
SITE_URL, SITE_NAME, FAVICON_512, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS,
FAVORITE_ROOMS, ACCOUNT_LOGIN_FORM, ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB,
ACCOUNT_GITLAB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR, ACCOUNT_TWITTER, ACCOUNT_WORDPRESS,
LDAP_ENABLE, ACCOUNT_REGISTRATION, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE,
UPLOAD_WHITELIST_MIMETYPES, HIDE_USER_JOIN, HIDE_USER_LEAVE, HIDE_TYPE_AU, HIDE_MUTE_UNMUTE,
HIDE_TYPE_RU, ACCOUNT_CUSTOM_FIELDS, ALLOW_MESSAGE_DELETING, ALLOW_MESSAGE_EDITING,
ALLOW_MESSAGE_PINNING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS)
FAVORITE_ROOMS, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE, UPLOAD_WHITELIST_MIMETYPES,
HIDE_USER_JOIN, HIDE_USER_LEAVE,
HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS)
suspend fun refresh(server: String) {
withContext(CommonPool) {
......
......@@ -7,20 +7,25 @@ typealias PublicSettings = Map<String, Value<Any>>
interface SettingsRepository {
fun save(url: String, settings: PublicSettings)
fun get(url: String): PublicSettings?
fun get(url: String): PublicSettings
}
// Authentication methods.
const val LDAP_ENABLE = "LDAP_Enable"
const val CAS_ENABLE = "CAS_enabled"
const val CAS_LOGIN_URL = "CAS_login_url"
const val ACCOUNT_REGISTRATION = "Accounts_RegistrationForm"
const val ACCOUNT_LOGIN_FORM = "Accounts_ShowFormLogin"
const val ACCOUNT_PASSWORD_RESET = "Accounts_PasswordReset"
const val ACCOUNT_CUSTOM_FIELDS = "Accounts_CustomFields"
const val ACCOUNT_GOOGLE = "Accounts_OAuth_Google"
const val ACCOUNT_FACEBOOK = "Accounts_OAuth_Facebook"
const val ACCOUNT_GITHUB = "Accounts_OAuth_Github"
const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab"
const val ACCOUNT_GOOGLE = "Accounts_OAuth_Google"
const val ACCOUNT_LINKEDIN = "Accounts_OAuth_Linkedin"
const val ACCOUNT_METEOR = "Accounts_OAuth_Meteor"
const val ACCOUNT_TWITTER = "Accounts_OAuth_Twitter"
const val ACCOUNT_WORDPRESS = "Accounts_OAuth_Wordpress"
const val ACCOUNT_REGISTRATION = "Accounts_RegistrationForm"
const val ACCOUNT_LOGIN_FORM = "Accounts_ShowFormLogin"
const val ACCOUNT_CUSTOM_FIELDS = "Accounts_CustomFields"
const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab"
const val SITE_URL = "Site_Url"
const val SITE_NAME = "Site_Name"
......@@ -28,7 +33,6 @@ const val FAVICON_512 = "Assets_favicon_512"
const val USE_REALNAME = "UI_Use_Real_Name"
const val ALLOW_ROOM_NAME_SPECIAL_CHARS = "UI_Allow_room_names_with_special_chars"
const val FAVORITE_ROOMS = "Favorite_Rooms"
const val LDAP_ENABLE = "LDAP_Enable"
const val UPLOAD_STORAGE_TYPE = "FileUpload_Storage_Type"
const val UPLOAD_MAX_FILE_SIZE = "FileUpload_MaxFileSize"
const val UPLOAD_WHITELIST_MIMETYPES = "FileUpload_MediaTypeWhiteList"
......@@ -42,23 +46,29 @@ const val ALLOW_MESSAGE_EDITING = "Message_AllowEditing"
const val SHOW_DELETED_STATUS = "Message_ShowDeletedStatus"
const val SHOW_EDITED_STATUS = "Message_ShowEditedStatus"
const val ALLOW_MESSAGE_PINNING = "Message_AllowPinning"
/*
* Extension functions for Public Settings.
*
* If you need to access a Setting, add a const val key above, add it to the filter on
* ServerPresenter.kt and a extension function to access it
*/
fun PublicSettings.googleEnabled(): Boolean = this[ACCOUNT_GOOGLE]?.value == true
fun PublicSettings.facebookEnabled(): Boolean = this[ACCOUNT_FACEBOOK]?.value == true
fun PublicSettings.githubEnabled(): Boolean = this[ACCOUNT_GITHUB]?.value == true
fun PublicSettings.linkedinEnabled(): Boolean = this[ACCOUNT_LINKEDIN]?.value == true
fun PublicSettings.meteorEnabled(): Boolean = this[ACCOUNT_METEOR]?.value == true
fun PublicSettings.twitterEnabled(): Boolean = this[ACCOUNT_TWITTER]?.value == true
fun PublicSettings.gitlabEnabled(): Boolean = this[ACCOUNT_GITLAB]?.value == true
fun PublicSettings.wordpressEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true
fun PublicSettings.isLdapAuthenticationEnabled(): Boolean = this[LDAP_ENABLE]?.value == true
fun PublicSettings.isCasAuthenticationEnabled(): Boolean = this[CAS_ENABLE]?.value == true
fun PublicSettings.casLoginUrl(): String = this[CAS_LOGIN_URL]?.value.toString()
fun PublicSettings.isRegistrationEnabledForNewUsers(): Boolean = this[ACCOUNT_REGISTRATION]?.value == "Public"
fun PublicSettings.isLoginFormEnabled(): Boolean = this[ACCOUNT_LOGIN_FORM]?.value == true
fun PublicSettings.isPasswordResetEnabled(): Boolean = this[ACCOUNT_PASSWORD_RESET]?.value == true
fun PublicSettings.isGoogleAuthenticationEnabled(): Boolean = this[ACCOUNT_GOOGLE]?.value == true
fun PublicSettings.isFacebookAuthenticationEnabled(): Boolean = this[ACCOUNT_FACEBOOK]?.value == true
fun PublicSettings.isGithubAuthenticationEnabled(): Boolean = this[ACCOUNT_GITHUB]?.value == true
fun PublicSettings.isLinkedinAuthenticationEnabled(): Boolean = this[ACCOUNT_LINKEDIN]?.value == true
fun PublicSettings.isMeteorAuthenticationEnabled(): Boolean = this[ACCOUNT_METEOR]?.value == true
fun PublicSettings.isTwitterAuthenticationEnabled(): Boolean = this[ACCOUNT_TWITTER]?.value == true
fun PublicSettings.isGitlabAuthenticationEnabled(): Boolean = this[ACCOUNT_GITLAB]?.value == true
fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true
fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true
fun PublicSettings.ldapEnabled(): Boolean = this[LDAP_ENABLE]?.value == true
// Message settings
fun PublicSettings.showDeletedStatus(): Boolean = this[SHOW_DELETED_STATUS]?.value == true
......@@ -67,11 +77,6 @@ fun PublicSettings.allowedMessagePinning(): Boolean = this[ALLOW_MESSAGE_PINNING
fun PublicSettings.allowedMessageEditing(): Boolean = this[ALLOW_MESSAGE_EDITING]?.value == true
fun PublicSettings.allowedMessageDeleting(): Boolean = this[ALLOW_MESSAGE_DELETING]?.value == true
fun PublicSettings.registrationEnabled(): Boolean {
val value = this[ACCOUNT_REGISTRATION]
return value?.value == "Public"
}
fun PublicSettings.uploadMimeTypeFilter(): Array<String> {
val values = this[UPLOAD_WHITELIST_MIMETYPES]?.value
values?.let { it as String }?.split(",")?.let {
......
......@@ -14,12 +14,10 @@ class SharedPreferencesSettingsRepository(private val localRepository: LocalRepo
localRepository.save("$SETTINGS_KEY$url", adapter.toJson(settings))
}
override fun get(url: String): PublicSettings? {
val settings = localRepository.get("$SETTINGS_KEY$url")
settings?.let {
return adapter.fromJson(it)
override fun get(url: String): PublicSettings {
val settings = localRepository.get("$SETTINGS_KEY$url")!!
settings.let {
return adapter.fromJson(it)!!
}
return null
}
}
\ No newline at end of file
package chat.rocket.android.util.extensions
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.support.v4.app.Fragment
import android.view.View
import android.view.ViewAnimationUtils
import android.view.animation.AccelerateInterpolator
......@@ -63,4 +72,37 @@ fun View.circularRevealOrUnreveal(centerX: Int, centerY: Int, startRadius: Float
}
anim.start()
}
fun View.shake(x: Float = 2F, num: Int = 0){
if (num == 6) {
this.translationX = 0.toFloat()
return
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationX", this.context.dp(x)))
animatorSet.duration = 50
animatorSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
shake(if (num == 5) 0.toFloat() else -x, num + 1)
}
})
animatorSet.start()
}
fun Context.dp(value: Float): Float {
val density = this.resources.displayMetrics.density
val result = Math.ceil(density.times(value.toDouble()))
return result.toFloat()
}
fun Fragment.vibrateSmartPhone() {
val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(200)
}
}
\ No newline at end of file
......@@ -9,6 +9,7 @@ import android.widget.TextView
import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import ru.noties.markwon.Markwon
import java.security.SecureRandom
fun String.ifEmpty(value: String): String {
if (isEmpty()) {
......@@ -34,6 +35,17 @@ fun EditText.erase() {
fun String.isEmailValid(): Boolean = Patterns.EMAIL_ADDRESS.matcher(this).matches()
fun generateRandomString(stringLength: Int): String {
val base = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
val secureRandom = SecureRandom()
val stringBuilder = StringBuilder(stringLength)
for (i in 0 until stringLength) {
stringBuilder.append(base[secureRandom.nextInt(base.length)])
}
return stringBuilder.toString()
}
var TextView.textContent: String
get() = text.toString()
set(value) {
......
package chat.rocket.android.webview.cas.ui
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.webkit.WebView
import android.webkit.WebViewClient
import chat.rocket.android.R
import kotlinx.android.synthetic.main.activity_web_view.*
import kotlinx.android.synthetic.main.app_bar.*
fun Context.webViewIntent(webPageUrl: String, casToken: String): Intent {
return Intent(this, CasWebViewActivity::class.java).apply {
putExtra(INTENT_WEB_PAGE_URL, webPageUrl)
putExtra(INTENT_CAS_TOKEN, casToken)
}
}
private const val INTENT_WEB_PAGE_URL = "web_page_url"
private const val INTENT_CAS_TOKEN = "cas_token"
class CasWebViewActivity : AppCompatActivity() {
private lateinit var webPageUrl: String
private lateinit var casToken: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
webPageUrl = intent.getStringExtra(INTENT_WEB_PAGE_URL)
requireNotNull(webPageUrl) { "no web_page_url provided in Intent extras" }
casToken = intent.getStringExtra(INTENT_CAS_TOKEN)
requireNotNull(casToken) { "no cas_token provided in Intent extras" }
setupToolbar()
}
override fun onResume() {
super.onResume()
setupWebView()
}
override fun onBackPressed() {
if (web_view.canGoBack()) {
web_view.goBack()
} else {
finishActivity(false)
}
}
private fun setupToolbar() {
toolbar.title = getString(R.string.title_authentication)
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp)
toolbar.setNavigationOnClickListener { finishActivity(false) }
}
@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
web_view.settings.javaScriptEnabled = true
web_view.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
// The user can be already logged in the CAS, so check if the URL contains the "ticket" word
// (that means he/she is successful authenticated and we don't need to wait until the page is finished.
if (url.contains("ticket")) {
finishActivity(true)
}
}
override fun onPageFinished(view: WebView, url: String) {
if (url.contains("ticket")) {
finishActivity(true)
} else {
view_loading.hide()
}
}
}
web_view.loadUrl(webPageUrl)
}
private fun finishActivity(setResultOk: Boolean) {
if (setResultOk) {
setResult(Activity.RESULT_OK, Intent().putExtra(INTENT_CAS_TOKEN, casToken))
finish()
} else {
super.onBackPressed()
}
overridePendingTransition(R.anim.hold, R.anim.slide_down)
}
}
\ No newline at end of file
package chat.rocket.android.webview
package chat.rocket.android.webview.ui
import android.annotation.SuppressLint
import android.content.Context
......@@ -19,6 +19,7 @@ fun Context.webViewIntent(webPageUrl: String): Intent {
private const val INTENT_WEB_PAGE_URL = "web_page_url"
// Simple WebView to load URL.
class WebViewActivity : AppCompatActivity() {
private lateinit var webPageUrl: String
......
......@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".webview.WebViewActivity">
tools:context=".webview.ui.WebViewActivity">
<include
android:id="@+id/layout_app_bar"
......
......@@ -45,6 +45,19 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username_or_email" />
<Button
android:id="@+id/button_cas"
style="@style/Authentication.Button"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp"
android:text="@string/action_login_or_sign_up"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password"
tools:visibility="visible" />
<TextView
android:id="@+id/text_new_to_rocket_chat"
android:layout_width="wrap_content"
......@@ -57,7 +70,7 @@
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password"
app:layout_constraintTop_toBottomOf="@+id/button_cas"
tools:visibility="visible" />
<com.wang.avi.AVLoadingIndicatorView
......@@ -69,7 +82,7 @@
app:indicatorName="BallPulseIndicator"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password"
app:layout_constraintTop_toBottomOf="@+id/button_cas"
tools:visibility="visible" />
<LinearLayout
......@@ -195,9 +208,8 @@
android:id="@+id/button_log_in"
style="@style/Authentication.Button"
android:text="@string/title_log_in"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="gone" />
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
</ScrollView>
\ No newline at end of file
......@@ -4,10 +4,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="12dp">
android:background="?android:attr/selectableItemBackground"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/chat_item_top_and_bottom_padding"
android:paddingBottom="@dimen/chat_item_top_and_bottom_padding">
<include
android:id="@+id/layout_avatar"
......
......@@ -4,10 +4,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="6dp">
android:background="?android:attr/selectableItemBackground"
android:paddingBottom="@dimen/member_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/member_item_top_and_bottom_padding">
<include
android:id="@+id/layout_avatar"
......
......@@ -4,10 +4,13 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="6dp">
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:focusable="true">
<include
android:id="@+id/layout_avatar"
......@@ -16,7 +19,30 @@
android:layout_height="40dp"
android:layout_marginTop="5dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/new_messages_notif" />
<LinearLayout
android:id="@+id/new_messages_notif"
tools:visibility="visible"
android:visibility="gone"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<View
android:layout_gravity="center"
android:layout_height="1dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_marginRight="4dp"
android:background="@color/red"/>
<TextView
android:layout_width="wrap_content"
android:text="@string/msg_unread_messages"
android:layout_height="wrap_content"
android:textColor="@color/red" />
</LinearLayout>
<LinearLayout
android:id="@+id/top_container"
......@@ -24,6 +50,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/new_messages_notif"
app:layout_constraintLeft_toRightOf="@+id/layout_avatar">
<TextView
......
......@@ -5,6 +5,7 @@
android:id="@+id/attachment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="vertical"
android:paddingEnd="@dimen/screen_edge_left_and_right_margins"
android:paddingStart="72dp">
......
......@@ -5,6 +5,7 @@
android:id="@+id/url_preview_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:paddingEnd="24dp"
android:paddingStart="72dp">
......
......@@ -4,6 +4,7 @@
<string name="title_sign_in_your_server">Faça login no seu servidor</string>
<string name="title_log_in">Entrar</string>
<string name="title_sign_up">Inscreva-se</string>
<string name="title_authentication">Autenticação</string>
<string name="title_legal_terms">Termos Legais</string>
<string name="title_chats">Chats</string>
<string name="title_profile">Perfil</string>
......@@ -14,6 +15,7 @@
<!-- Actions -->
<string name="action_connect">Conectar</string>
<string name="action_login_or_sign_up">Toque este botão para fazer login ou criar uma conta</string>
<string name="action_terms_of_service">Termos de Serviço</string>
<string name="action_privacy_policy">Política de Privacidade</string>
<string name="action_search">Pesquisar</string>
......@@ -64,6 +66,7 @@
<string name="msg_utc_offset">Deslocamento de UTC</string>
<string name="msg_new_password">Informe a nova senha</string>
<string name="msg_confirm_password">Confirme a nova senha</string>
<string name="msg_unread_messages">Mensagens não lidas</string>
<!-- System messages -->
<string name="message_room_name_changed">Nome da sala alterado para: %1$s por %2$s</string>
......
......@@ -4,6 +4,11 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="screen_edge_left_and_right_margins">16dp</dimen>
<dimen name="screen_edge_left_and_right_padding">16dp</dimen>
<dimen name="chat_item_top_and_bottom_padding">12dp</dimen>
<dimen name="message_item_top_and_bottom_padding">6dp</dimen>
<dimen name="member_item_top_and_bottom_padding">6dp</dimen>
<dimen name="edit_text_margin">10dp</dimen>
<dimen name="edit_text_drawable_padding">16dp</dimen>
......
......@@ -5,6 +5,7 @@
<string name="title_sign_in_your_server">Sign in your server</string>
<string name="title_log_in">Log in</string>
<string name="title_sign_up">Sign up</string>
<string name="title_authentication">Authentication</string>
<string name="title_legal_terms">Legal Terms</string>
<string name="title_chats">Chats</string>
<string name="title_profile">Profile</string>
......@@ -15,6 +16,7 @@
<!-- Actions -->
<string name="action_connect">Connect</string>
<string name="action_login_or_sign_up">Tap this button to log in or create an account</string>
<string name="action_terms_of_service">Terms of Service</string>
<string name="action_privacy_policy">Privacy Policy</string>
<string name="action_search">Search</string>
......@@ -66,6 +68,7 @@
<string name="msg_utc_offset">UTC offset</string>
<string name="msg_new_password">Enter New Password</string>
<string name="msg_confirm_password">Confirm New Password</string>
<string name="msg_unread_messages">Unread messages</string>
<!-- System messages -->
<string name="message_room_name_changed">Room name changed to: %1$s by %2$s</string>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment