Unverified Commit 3242e0c5 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito Committed by GitHub

Merge pull request #652 from filipedelimabrito/layout/finishing-authentication-screens

[LAYOUT] Finishing authentication screens
parents f245c5d0 7e61d679
......@@ -76,6 +76,8 @@ dependencies {
implementation libraries.androidSvg
implementation libraries.aVLoadingIndicatorView
testImplementation libraries.junit
androidTestImplementation (libraries.expressoCore , {
exclude group: 'com.android.support', module: 'support-annotations'
......
......@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".app.RocketChatApplication"
......@@ -18,7 +19,8 @@
android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation"
android:screenOrientation="portrait"
android:theme="@style/AuthenticationTheme">
android:theme="@style/AuthenticationTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
......@@ -33,8 +35,11 @@
<activity
android:name=".app.ChatRoomActivity"
android:theme="@style/AppTheme">
</activity>
android:theme="@style/AppTheme" />
<activity
android:name=".webview.WebViewActivity"
android:theme="@style/AppTheme" />
</application>
</manifest>
\ No newline at end of file
package chat.rocket.android.app
import android.app.Activity
import android.graphics.Rect
import android.util.Log
import android.view.View
import android.view.ViewTreeObserver
import android.widget.FrameLayout
//TODO: check if this code has memory leak.
class LayoutHelper {
private var childOfContent: View? = null
private var usableHeightPrevious: Int = 0
private var frameLayoutParams: FrameLayout.LayoutParams? = null
/**
* Workaround to adjust the layout when in the full screen mode.
*
* The original author of this code is Joseph Johnson and you can see his answer here: https://stackoverflow.com/a/19494006/4744263
*
* Note that this function has some differences from the original, like using *frameLayoutParams.height = usableHeightNow* instead of
* *frameLayoutParams.height = usableHeightSansKeyboard* (RobertoAllende's comment - from the same link above).
*
* @param activity The Activity to adjust the layout.
*/
fun install(activity: Activity) {
try {
val content = activity.findViewById<View>(android.R.id.content) as FrameLayout
childOfContent = content.getChildAt(0)
childOfContent?.viewTreeObserver?.addOnGlobalLayoutListener(listener)
frameLayoutParams = childOfContent?.layoutParams as FrameLayout.LayoutParams
} catch (exception : ClassCastException) {
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
Log.e("ERROR", exception.message)
}
}
private val listener = ViewTreeObserver.OnGlobalLayoutListener {
resizeChildOfContent()
}
private fun resizeChildOfContent() {
val usableHeightNow = computeUsableHeight()
if (usableHeightNow != usableHeightPrevious) {
val usableHeightSansKeyboard = childOfContent?.rootView?.height ?: 0
val heightDifference = usableHeightSansKeyboard - usableHeightNow
if (heightDifference > usableHeightSansKeyboard / 4) {
// keyboard probably just became visible
frameLayoutParams?.height = usableHeightSansKeyboard - heightDifference
} else {
// keyboard probably just became hidden
frameLayoutParams?.height = usableHeightNow
}
childOfContent?.requestLayout()
usableHeightPrevious = usableHeightNow
}
}
private fun computeUsableHeight(): Int {
val rect = Rect()
childOfContent?.getWindowVisibleDisplayFrame(rect)
return rect.bottom - rect.top
}
fun remove() {
childOfContent?.viewTreeObserver?.removeOnGlobalLayoutListener(listener)
childOfContent = null
frameLayoutParams = null
}
}
\ No newline at end of file
package chat.rocket.android.authentication.di
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import android.content.Context
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.scope.PerActivity
......@@ -13,13 +13,7 @@ class AuthenticationModule {
@Provides
@PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity) = AuthenticationNavigator(activity)
@Provides
@PerActivity
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context)
@Provides
fun provideJob(): Job {
......
......@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class LoginFragmentModule {
@Provides
fun loginView(frag: LoginFragment): LoginView {
return frag
......@@ -26,4 +27,4 @@ class LoginFragmentModule {
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject
class LoginPresenter @Inject constructor(private val view: LoginView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
websocketUrl = navigator.currentServer!!
tokenRepository = repository
platformLogger = logger
}
fun authenticate(username: String, password: String) {
// TODO - validate input
fun authenticate(usernameOrEmail: String, password: String) {
when {
usernameOrEmail.isBlank() -> {
view.alertWrongUsernameOrEmail()
}
password.isEmpty() -> {
view.alertWrongPassword()
}
else -> {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
launchUI(strategy) {
view.showLoading()
try {
val token = client.login(username, password)
try {
client.login(usernameOrEmail, password) // TODO This function returns a user token so should we save it?
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatTwoFactorException) {
navigator.toTwoFA(usernameOrEmail, password)
} else {
val message = exception.message
if (message != null) {
view.showMessage(message)
} else {
view.showGenericErrorMessage()
}
}
}
navigator.toChatList()
} catch (ex: RocketChatException) {
when(ex) {
is RocketChatTwoFactorException ->
navigator.toTwoFA(navigator.currentServer!!, username, password)
else ->
view.onLoginError(ex.message)
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
} finally {
view.hideLoading()
}
}
}
fun signup() {
navigator.toSignUp(navigator.currentServer!!)
navigator.toSignUp()
}
}
\ No newline at end of file
package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface LoginView : LoadingView {
fun onLoginError(message: String?)
interface LoginView : LoadingView, MessageView, InternetView {
/**
* Shows the oauth view if the server settings allow the login via social accounts.
*
* 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)
/**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
*/
fun setupFabListener()
/**
* Shows the login by Facebook view.
*/
fun enableLoginByFacebook()
/**
* Shows the login by Github view.
*/
fun enableLoginByGithub()
/**
* Shows the login by Google view.
*/
fun enableLoginByGoogle()
/**
* Shows the login by Linkedin view.
*/
fun enableLoginByLinkedin()
/**
* Shows the login by Meteor view.
*/
fun enableLoginByMeteor()
/**
* Shows the login by Twitter view.
*/
fun enableLoginByTwitter()
/**
* Shows the login by Gitlab view.
*/
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.
*/
fun showSignUpView(value: Boolean)
/**
* Alerts the user about a wrong inputted username or email.
*/
fun alertWrongUsernameOrEmail()
/**
* Alerts the user about a wrong inputted password.
*/
fun alertWrongPassword()
}
\ No newline at end of file
package chat.rocket.android.authentication.login.ui
import DrawableHelper
import android.app.ProgressDialog
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.style.ClickableSpan
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.ScrollView
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.app.KeyboardHelper
import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import javax.inject.Inject
class LoginFragment : Fragment(), LoginView {
@Inject lateinit var presenter: LoginPresenter
@Inject lateinit var appContext: Context // TODO we really need it? Check alternatives...
companion object {
private const val SERVER_URL = "server_url"
fun newInstance(url: String) = LoginFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_URL, url)
/*
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(scroll_view.rootView)) {
showSignUpView(false)
showOauthView(false)
showLoginButton(true)
} else {
if (isEditTextEmpty()) {
showSignUpView(true)
showOauthView(true)
showLoginButton(false)
}
}
}
var progress: ProgressDialog? = null
lateinit var serverUrl: String
@Inject
lateinit var presenter: LoginPresenter
private var isGlobalLayoutListenerSetUp = false
*/
companion object {
fun newInstance() = LoginFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
// TODO - research a better way to initialize parameters on fragments.
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_log_in, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_authentication_log_in)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
activity?.apply {
text_username_or_email.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(text_username_or_email, InputMethodManager.SHOW_IMPLICIT)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
// Just an example: if the server allow the login via social accounts (oauth authentication) then show the respective interface.
shouldShowOauthView(true)
// In this case we need to setup the layout to hide and show the oauth interface when the soft keyboard is shown (means that the user touched the text_username_or_email and text_password EditText).
setupGlobalLayoutListener()
button_log_in.setOnClickListener {
presenter.authenticate(text_username_or_email.textContent, text_password.textContent)
}
/*
// TODO: THIS IS A PRESENTER CONCERN - REMOVE THAT ! WE SHOULD GET THE SERVER SETTINGS!
// -------------------------------------------------------------------------------------------------------------------
showOauthView(true)
// Show the first three social account's ImageButton (REMARK: we must show at maximum *three* views)
showLoginUsingFacebookImageButton()
showLoginUsingGithubImageButton()
showLoginUsingGoogleImageButton()
enableLoginByFacebook()
enableLoginByGithub()
enableLoginByGoogle()
// Setup the FloatingActionButton to show more social account's ImageButton (it expands the social accounts interface to show more views).
setupFabListener()
// Just an example: if the server allow the new users registration then show the respective interface.
shouldShowSignUpMsgView(true)
button_log_in.setOnClickListener {
presenter.authenticate(text_username_or_email.text.toString(), text_password.text.toString())
}
text_new_to_rocket_chat.setOnClickListener {
presenter.signup()
}
setupSignUpListener()
showSignUpView(true)
// -------------------------------------------------------------------------------------------------------------------
*/
}
/*
override fun onDestroyView() {
scroll_view.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
super.onDestroyView()
}
private fun tintEditTextDrawableStart() {
activity?.applicationContext?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_username_or_email, text_password), drawables)
if (isGlobalLayoutListenerSetUp) {
scroll_view.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
isGlobalLayoutListenerSetUp = false
}
}
*/
override fun showOauthView(value: Boolean) {
// if (value) {
// social_accounts_container.setVisibility(true)
// button_fab.setVisibility(true)
//
// // We need to setup the layout to hide and show the oauth interface when the soft keyboard is shown
// // (means that the user touched the text_username_or_email or text_password EditText to fill that respective fields).
// if (!isGlobalLayoutListenerSetUp) {
// scroll_view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
// isGlobalLayoutListenerSetUp = true
// }
// } else {
// social_accounts_container.setVisibility(false)
// button_fab.setVisibility(false)
// }
}
private fun setupGlobalLayoutListener() {
scroll_view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
override fun setupFabListener() {
// button_fab.setOnClickListener({
// button_fab.hide()
// showRemainingSocialAccountsView()
// scrollToBottom()
// })
}
val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(scroll_view.rootView)) {
shouldShowOauthView(false)
shouldShowSignUpMsgView(false)
shouldShowLoginButton(true)
} else {
if (isEditTextNullOrBlank()) {
shouldShowOauthView(true)
shouldShowSignUpMsgView(true)
shouldShowLoginButton(false)
}
}
override fun enableLoginByFacebook() {
button_facebook.setVisibility(true)
}
private fun shouldShowOauthView(show: Boolean) {
if (show) {
social_accounts_container.visibility = View.VISIBLE
button_fab.visibility = View.VISIBLE
} else {
social_accounts_container.visibility = View.GONE
button_fab.visibility = View.GONE
}
override fun enableLoginByGithub() {
button_github.setVisibility(true)
}
private fun shouldShowSignUpMsgView(show: Boolean) {
if (show) {
text_new_to_rocket_chat.visibility = View.VISIBLE
} else {
text_new_to_rocket_chat.visibility = View.GONE
}
override fun enableLoginByGoogle() {
button_google.setVisibility(true)
}
private fun shouldShowLoginButton(show: Boolean) {
if (show) {
button_log_in.visibility = View.VISIBLE
} else {
button_log_in.visibility = View.GONE
}
override fun enableLoginByLinkedin() {
button_linkedin.setVisibility(true)
}
private fun showLoginUsingFacebookImageButton() {
button_facebook.visibility = View.VISIBLE
override fun enableLoginByMeteor() {
button_meteor.setVisibility(true)
}
private fun showLoginUsingGithubImageButton() {
button_github.visibility = View.VISIBLE
override fun enableLoginByTwitter() {
button_twitter.setVisibility(true)
}
private fun showLoginUsingGoogleImageButton() {
button_google.visibility = View.VISIBLE
override fun enableLoginByGitlab() {
button_gitlab.setVisibility(true)
}
private fun showLoginUsingLinkedinImageButton() {
button_linkedin.visibility = View.VISIBLE
override fun showSignUpView(value: Boolean) = text_new_to_rocket_chat.setVisibility(value)
override fun alertWrongUsernameOrEmail() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_username_or_email)
text_username_or_email.requestFocus()
}
private fun showLoginUsingMeteorImageButton() {
button_meteor.visibility = View.VISIBLE
override fun alertWrongPassword() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_password)
text_password.requestFocus()
}
private fun showLoginUsingTwitterImageButton() {
button_twitter.visibility = View.VISIBLE
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
}
private fun showLoginUsingGitlabImageButton() {
button_gitlab.visibility = View.VISIBLE
override fun hideLoading() {
view_loading.setVisibility(false)
enableUserInput(true)
}
private fun setupFabListener() {
button_fab.setOnClickListener({
showLoginUsingLinkedinImageButton()
showLoginUsingMeteorImageButton()
showLoginUsingTwitterImageButton()
showLoginUsingGitlabImageButton()
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
scrollToBottom()
hideFab()
})
}
// Returns true if *all* EditText are null or blank.
private fun isEditTextNullOrBlank(): Boolean {
return text_username_or_email.text.isNullOrBlank() && text_password.text.isNullOrBlank()
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun scrollToBottom() {
scroll_view.postDelayed({
scroll_view.fullScroll(ScrollView.FOCUS_DOWN)
}, 1000)
}
private fun hideFab() {
button_fab.postDelayed({
button_fab.hide()
}, 1500)
}
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection))
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating",
"Verifying user credentials", true, true)
private fun tintEditTextDrawableStart() {
activity?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_username_or_email, text_password), drawables)
}
}
/*
private fun showLoginButton(value: Boolean) {
button_log_in.setVisibility(value)
}
override fun hideLoading() {
progress?.apply {
cancel()
private fun setupSignUpListener() {
val signUp = getString(R.string.title_sign_up)
val newToRocketChat = String.format(getString(R.string.msg_new_to_rocket_chat), signUp)
text_new_to_rocket_chat.text = newToRocketChat
val signUpListener = object : ClickableSpan() {
override fun onClick(view: View) = presenter.signup()
}
progress = null
TextHelper.addLink(text_new_to_rocket_chat, arrayOf(signUp), arrayOf(signUpListener))
}
*/
private fun enableUserInput(value: Boolean) {
button_log_in.isEnabled = value
text_username_or_email.isEnabled = value
text_password.isEnabled = value
// if (isEditTextEmpty()) {
// showSignUpView(value)
// showOauthView(value)
// }
}
/*
// Returns true if *all* EditTexts are empty.
private fun isEditTextEmpty(): Boolean = text_username_or_email.textContent.isBlank() && text_password.textContent.isEmpty()
private fun showRemainingSocialAccountsView() {
social_accounts_container.postDelayed({
enableLoginByLinkedin()
enableLoginByMeteor()
enableLoginByTwitter()
enableLoginByGitlab()
}, 1000)
}
override fun onLoginError(message: String?) {
// TODO - show a proper error message
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
private fun scrollToBottom() {
scroll_view.postDelayed({
scroll_view.fullScroll(ScrollView.FOCUS_DOWN)
}, 1250)
}
*/
}
\ No newline at end of file
package chat.rocket.android.authentication.presentation
import android.content.Context
import android.content.Intent
import chat.rocket.android.R
import chat.rocket.android.app.MainActivity
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.util.addFragmentBackStack
import chat.rocket.android.webview.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
var currentServer: String? = null
class AuthenticationNavigator(internal val activity: AuthenticationActivity, internal val context: Context) {
lateinit var server: String
lateinit var usernameOrEmail: String
lateinit var password: String
fun toLogin(server: String) {
currentServer = server
this.server = server
activity.addFragmentBackStack("loginFragment", R.id.fragment_container) {
LoginFragment.newInstance(server)
LoginFragment.newInstance()
}
}
fun toChatList() {
val chatRoom = Intent(activity, MainActivity::class.java).apply {
//TODO any parameter to pass
fun toTwoFA(usernameOrEmail: String, password: String) {
this.usernameOrEmail = usernameOrEmail
this.password = password
activity.addFragmentBackStack("twoFAFragment", R.id.fragment_container) {
TwoFAFragment.newInstance()
}
activity.startActivity(chatRoom)
activity.finish()
}
fun toTwoFA(server: String, username: String, password: String) {
currentServer = server
activity.addFragmentBackStack("twoFAFragment", R.id.fragment_container) {
TwoFAFragment.newInstance(server, username, password)
fun toSignUp() {
activity.addFragmentBackStack("signupFragment", R.id.fragment_container) {
SignupFragment.newInstance()
}
}
fun toSignUp(server: String) {
currentServer = server
activity.addFragmentBackStack("signupFragment", R.id.fragment_container) {
SignupFragment.newInstance(server)
fun toTermsOfService() {
val webPageUrl = server + "/terms-of-service" // TODO Move to UrlHelper
activity.startActivity(context.webViewIntent(webPageUrl))
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
fun toPrivacyPolicy() {
val webPageUrl = server + "/privacy-policy" // TODO Move to UrlHelper
activity.startActivity(context.webViewIntent(webPageUrl))
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
fun toChatList() {
val chatRoom = Intent(activity, MainActivity::class.java).apply {
//TODO any parameter to pass
}
activity.startActivity(chatRoom)
activity.finish()
}
}
package chat.rocket.android.authentication.server.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class ServerFragmentModule {
@Provides
fun serverView(frag: ServerFragment): ServerView {
return frag
}
}
@Provides
fun provideLifecycleOwner(frag: ServerFragment): 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.server.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI
import chat.rocket.core.RocketChatClient
import javax.inject.Inject
class ServerPresenter @Inject constructor(private val view: ServerView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
fun login(server: String) {
// TODO - validate server URL and get server settings and info before going to Login screen
navigator.toLogin(server)
fun connect(server: String) {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
// TODO - validate server URL and get server settings and info before going to Login screen
//client.connect(server)
navigator.toLogin(server)
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.server.presentation
interface ServerView
\ No newline at end of file
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface ServerView : LoadingView, MessageView, InternetView
\ No newline at end of file
......@@ -5,44 +5,70 @@ import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.ViewTreeObserver
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.authentication.server.presentation.ServerPresenter
import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.util.ifEmpty
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.util.*
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_server.*
import javax.inject.Inject
class ServerFragment : Fragment(), ServerView {
@Inject lateinit var presenter: ServerPresenter
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
text_server_url.isCursorVisible = KeyboardHelper.isSoftKeyboardShown(relative_layout.rootView)
}
@Inject
lateinit var presenter: ServerPresenter
companion object {
fun newInstance() = ServerFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_authentication_server, container, false)
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)
relative_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
setupOnClickListener()
}
text_server_url.setSelection(text_server_url.length())
override fun onDestroyView() {
super.onDestroyView()
relative_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
}
button_connect.setOnClickListener {
val url = text_server_url.text.toString().ifEmpty(text_server_url.hint.toString())
presenter.login(server_protocol_label.text.toString() + url)
}
override fun hideLoading() {
view_loading.setVisibility(false)
enableUserInput(true)
}
companion object {
fun newInstance() = ServerFragment()
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection))
private fun enableUserInput(value: Boolean) {
button_connect.isEnabled = value
text_server_url.isEnabled = value
}
private fun setupOnClickListener() {
button_connect.setOnClickListener {
val url = text_server_url.textContent.ifEmpty(text_server_url.hintContent)
presenter.connect(text_server_protocol.textContent + url)
}
}
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.signup
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import timber.log.Timber
import javax.inject.Inject
class SignupPresenter @Inject constructor(private val view: SignupView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
websocketUrl = navigator.currentServer!!
tokenRepository = repository
platformLogger = logger
}
fun signup(email: String, name: String, username: String, password: String) {
// TODO - validate input
launchUI(strategy) {
view.showLoading()
try {
val user = client.signup(email, name, username, password)
Timber.d("Created user: $user")
fun signup(name: String, username: String, password: String, email: String) {
when {
name.isBlank() -> {
view.alertBlankName()
}
username.isBlank() -> {
view.alertBlankUsername()
}
password.isEmpty() -> {
view.alertEmptyPassword()
}
email.isBlank() -> {
view.alertBlankEmail()
}
else -> {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
val token = client.login(username, password)
Timber.d("Logged in: $token")
try {
client.signup(email, name, username, password) // TODO This function returns a user so should we save it?
client.login(username, password) // TODO This function returns a user token so should we save it?
navigator.toChatList()
} catch (exception: RocketChatException) {
val errorMessage = exception.message
if (errorMessage != null) {
view.showMessage(errorMessage)
} else {
view.showGenericErrorMessage()
}
}
navigator.toChatList()
} catch (ex: RocketChatException) {
view.onSignupError(ex.message)
} finally {
view.hideLoading()
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
}
fun termsOfService() {
navigator.toTermsOfService()
}
fun privacyPolicy() {
navigator.toPrivacyPolicy()
}
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface SignupView : LoadingView {
fun onSignupError(message: String? = "Unknown error")
interface SignupView : LoadingView, MessageView, InternetView {
/**
* Alerts the user about a blank name.
*/
fun alertBlankName()
/**
* Alerts the user about a blank username.
*/
fun alertBlankUsername()
/**
* Alerts the user about a empty password.
*/
fun alertEmptyPassword()
/**
* Alerts the user about a blank email.
*/
fun alertBlankEmail()
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.ui
import DrawableHelper
import android.app.ProgressDialog
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.style.ClickableSpan
import android.view.*
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.app.KeyboardHelper
import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.util.content
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
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
companion object {
private const val SERVER_URL = "server_url"
fun newInstance(url: String) = SignupFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_URL, url)
}
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.setVisibility(false)
} else {
text_new_user_agreement.setVisibility(true)
}
}
@Inject
lateinit var presenter: SignupPresenter
var progress: ProgressDialog? = null
lateinit var serverUrl: String
companion object {
fun newInstance() = SignupFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
// TODO - research a better way to initialize parameters on fragments.
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_sign_up, container, false)
......@@ -52,65 +52,105 @@ class SignupFragment : Fragment(), SignupView {
tintEditTextDrawableStart()
}
setupGlobalLayoutListener()
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
button_sign_up.setOnClickListener {
val email = text_email.content
val name = text_name.content
val username = text_username.content
val password = text_password.content
setUpNewUserAgreementListener()
presenter.signup(email, name, username, password)
button_sign_up.setOnClickListener {
presenter.signup(text_name.textContent, text_username.textContent, text_password.textContent, text_email.textContent)
}
}
override fun onDestroyView() {
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
super.onDestroyView()
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
private fun tintEditTextDrawableStart() {
activity?.applicationContext?.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)
}
override fun alertBlankName() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_name)
text_name.requestFocus()
}
private fun setupGlobalLayoutListener() {
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
override fun alertBlankUsername() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_username)
text_username.requestFocus()
}
val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.visibility = View.GONE
} else {
text_new_user_agreement.visibility = View.VISIBLE
}
override fun alertEmptyPassword() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_password)
text_password.requestFocus()
}
override fun alertBlankEmail() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_email)
text_email.requestFocus()
}
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating",
"Registering user", true, true)
enableUserInput(false)
view_loading.setVisibility(true)
}
override fun hideLoading() {
progress?.apply {
cancel()
}
progress = null
view_loading.setVisibility(false)
enableUserInput(true)
}
override fun onSignupError(message: String?) {
// TODO - show a proper error message
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
override fun showMessage(message: String) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
override fun showGenericErrorMessage() {
showMessage(getString(R.string.msg_generic_error))
}
override fun showNoInternetConnection() {
Toast.makeText(activity, getString(R.string.msg_no_internet_connection), Toast.LENGTH_SHORT).show()
}
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)
}
private fun setUpNewUserAgreementListener() {
val termsOfService = getString(R.string.action_terms_of_service)
val privacyPolicy = getString(R.string.action_privacy_policy)
val newUserAgreement = String.format(getString(R.string.msg_new_user_agreement), termsOfService, privacyPolicy)
text_new_user_agreement.text = newUserAgreement
val termsOfServiceListener = object : ClickableSpan() {
override fun onClick(view: View) {
presenter.termsOfService()
}
}
val privacyPolicyListener = object : ClickableSpan() {
override fun onClick(view: View) {
presenter.privacyPolicy()
}
}
TextHelper.addLink(text_new_user_agreement, arrayOf(termsOfService, privacyPolicy), arrayOf(termsOfServiceListener, privacyPolicyListener))
}
private fun enableUserInput(value: Boolean) {
button_sign_up.isEnabled = value
text_name.isEnabled = value
text_username.isEnabled = value
text_password.isEnabled = value
text_email.isEnabled = value
}
}
\ No newline at end of file
package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject
class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
websocketUrl = navigator.currentServer!!
tokenRepository = repository
platformLogger = logger
}
fun authenticate(username: String, password: String, pin: String) {
// TODO - validate input
fun authenticate(twoFactorAuthenticationCode: String) {
if (twoFactorAuthenticationCode.isBlank()) {
view.alertBlankTwoFactorAuthenticationCode()
} else {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
launchUI(strategy) {
view.showLoading()
try {
val token = client.login(username, password, pin)
try {
client.login(navigator.usernameOrEmail, navigator.password, twoFactorAuthenticationCode) // TODO This function returns a user token so should we save it?
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) {
view.alertInvalidTwoFactorAuthenticationCode()
} else {
val message = exception.message
if (message != null) {
view.showMessage(message)
} else {
view.showGenericErrorMessage()
}
}
}
navigator.toChatList()
} catch (ex: RocketChatException) {
view.onLoginError(ex.message)
} finally {
view.hideLoading()
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
fun signup() {
navigator.toSignUp(navigator.currentServer!!)
navigator.toSignUp()
}
}
\ No newline at end of file
package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface TwoFAView : LoginView
\ No newline at end of file
interface TwoFAView : LoadingView, MessageView, InternetView {
/**
* Alerts the user about a blank Two Factor Authentication code.
*/
fun alertBlankTwoFactorAuthenticationCode()
/**
* Alerts the user about an invalid inputted Two Factor Authentication code.
*/
fun alertInvalidTwoFactorAuthenticationCode()
}
\ No newline at end of file
package chat.rocket.android.authentication.twofactor.ui
import DrawableHelper
import android.app.ProgressDialog
import android.content.Context
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.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
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.util.content
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import javax.inject.Inject
class TwoFAFragment : Fragment(), TwoFAView {
@Inject lateinit var presenter: TwoFAPresenter
@Inject lateinit var appContext: Context // TODO we really need it? Check alternatives...
companion object {
private const val SERVER_URL = "server_url"
private const val USERNAME = "username"
private const val PASSWORD = "password"
fun newInstance(url: String, username: String, password: String) = TwoFAFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_URL, url)
putString(USERNAME, username)
putString(PASSWORD, password)
}
}
fun newInstance() = TwoFAFragment()
}
var progress: ProgressDialog? = null
lateinit var serverUrl: String
lateinit var username: String
lateinit var password: String
@Inject
lateinit var presenter: TwoFAPresenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
// TODO - research a better way to initialize parameters on fragments.
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
username = arguments?.getString(USERNAME) ?: ""
password = arguments?.getString(PASSWORD) ?: ""
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_two_fa, container, false)
......@@ -57,42 +38,58 @@ class TwoFAFragment : Fragment(), TwoFAView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
activity?.apply {
text_two_factor_auth.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(text_two_factor_auth, InputMethodManager.SHOW_IMPLICIT)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
setupOnClickListener()
}
button_log_in.setOnClickListener {
presenter.authenticate(username, password, text_two_factor_auth.content)
}
override fun alertBlankTwoFactorAuthenticationCode() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_two_factor_auth)
}
override fun alertInvalidTwoFactorAuthenticationCode() = showMessage(getString(R.string.msg_invalid_2fa_code))
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
}
override fun hideLoading() {
view_loading.setVisibility(false)
enableUserInput(true)
}
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection))
private fun tintEditTextDrawableStart() {
activity?.applicationContext?.apply {
activity?.apply {
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, this)
DrawableHelper.wrapDrawable(lockDrawable)
DrawableHelper.tintDrawable(lockDrawable, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_two_factor_auth, lockDrawable)
}
}
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating",
"Verifying user credentials", true, true)
private fun enableUserInput(value: Boolean) {
button_log_in.isEnabled = value
text_two_factor_auth.isEnabled = value
}
override fun hideLoading() {
progress?.apply {
cancel()
private fun setupOnClickListener() {
button_log_in.setOnClickListener {
presenter.authenticate(text_two_factor_auth.textContent)
}
progress = null
}
override fun onLoginError(message: String?) {
// TODO - show a proper error message
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
}
}
\ No newline at end of file
......@@ -4,7 +4,6 @@ import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.app.LayoutHelper
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.util.addFragment
import dagger.android.AndroidInjection
......@@ -14,29 +13,19 @@ import dagger.android.support.HasSupportFragmentInjector
import javax.inject.Inject
class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
val layoutHelper = LayoutHelper()
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidInjection.inject(this)
setContentView(R.layout.activity_authentication)
layoutHelper.install(this)
AndroidInjection.inject(this)
addFragment("authenticationServerFragment", R.id.fragment_container) {
ServerFragment.newInstance()
}
}
override fun onDestroy() {
layoutHelper.remove()
super.onDestroy()
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
......
package chat.rocket.android.core.behaviours
interface InternetView {
fun showNoInternetConnection()
}
\ No newline at end of file
package chat.rocket.android.core.behaviours
interface LoadingView {
fun showLoading()
fun hideLoading()
}
\ No newline at end of file
package chat.rocket.android.core.behaviours
interface MessageView {
fun showMessage(message: String)
fun showGenericErrorMessage()
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import kotlinx.coroutines.experimental.Job
import javax.inject.Inject
class CancelStrategy @Inject constructor(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver {
init {
owner.lifecycle.addObserver(this)
}
......
......@@ -10,7 +10,7 @@ import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
@Singleton
@Component(modules = arrayOf(AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class))
@Component(modules = [AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class])
interface AppComponent {
@Component.Builder
......
......@@ -11,19 +11,17 @@ import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class ActivityBuilder {
@Module abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(modules = arrayOf(
AuthenticationModule::class,
@ContributesAndroidInjector(modules = [AuthenticationModule::class,
LoginFragmentProvider::class,
ServerFragmentProvider::class,
SignupFragmentProvider::class,
TwoFAFragmentProvider::class
))
])
abstract fun bindAuthenticationActivity(): AuthenticationActivity
@ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
@ContributesAndroidInjector abstract fun bindMainActivity(): MainActivity
}
......@@ -5,11 +5,14 @@ import android.arch.persistence.room.Room
import android.content.Context
import chat.rocket.android.BuildConfig
import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.util.TimberLogger
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import dagger.Module
import dagger.Provides
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton
......@@ -17,6 +20,20 @@ import javax.inject.Singleton
@Module
class AppModule {
@Provides
@Singleton
fun provideRocketChatClient(okHttpClient: OkHttpClient, repository: AuthTokenRepository, logger: PlatformLogger): RocketChatClient {
return RocketChatClient.create {
httpClient = okHttpClient
tokenRepository = repository
platformLogger = logger
// TODO remove
restUrl = HttpUrl.parse("https://open.rocket.chat")!!
websocketUrl = "https://open.rocket.chat"
}
}
@Provides
@Singleton
fun provideRocketChatDatabase(context: Application): RocketChatDatabase {
......@@ -35,20 +52,6 @@ class AppModule {
return database.serverDao()
}
/*@Provides
@Singleton
@IntoSet
fun provideHttpLoggingInterceptor(): Interceptor {
val interceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
interceptor.level = HttpLoggingInterceptor.Level.HEADERS
}
return interceptor
}*/
@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
......@@ -70,9 +73,15 @@ class AppModule {
}.build()
}
@Provides
@Singleton
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
@Provides
@Singleton
fun providePlatformLogger(): PlatformLogger {
return TimberLogger()
return TimberLogger
}
}
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
package chat.rocket.android.app
package chat.rocket.android.helper
import android.graphics.Rect
import android.view.View
......
package chat.rocket.android.helper
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.run
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Socket
object NetworkHelper {
/**
* Checks whether there is internet access.
*
* The original author of this code is Levit and you can see his answer here: https://stackoverflow.com/a/27312494/4744263
*
* @return true if there is internet access, false otherwise.
*/
suspend fun hasInternetAccess(): Boolean = run(CommonPool) {
try {
val socket = Socket()
val inetSocketAddress = InetSocketAddress("8.8.8.8", 53)
socket.connect(inetSocketAddress, 1500)
socket.close()
true
} catch (e: IOException) {
false
}
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.text.SpannableString
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.widget.TextView
object TextHelper {
/**
* Adds clickable link(s) to a TextView.
*
* @param textView The TextView to add clickable links.
* @param links The TextView content(s) to act as clickable links.
* @param clickableSpans The ClickableSpan(s) to handle the onClick listener.
*/
fun addLink(textView: TextView, links: Array<String>, clickableSpans: Array<ClickableSpan>) {
val spannableString = SpannableString(textView.text)
for (i in links.indices) {
val clickableSpan = clickableSpans[i]
val link = links[i]
val startIndexOfLink = textView.text.indexOf(link)
spannableString.setSpan(clickableSpan, startIndexOfLink, startIndexOfLink + link.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
textView.movementMethod = LinkMovementMethod.getInstance()
textView.setText(spannableString, TextView.BufferType.SPANNABLE)
}
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ package chat.rocket.android.util
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
......@@ -10,5 +11,9 @@ fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () ->
fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
supportFragmentManager.beginTransaction().replace(layoutId, fragment, tag).addToBackStack(tag).commit()
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right)
.replace(layoutId, fragment, tag)
.addToBackStack(tag)
.commit()
}
\ No newline at end of file
package chat.rocket.android.util
import android.support.annotation.LayoutRes
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
fun String.ifEmpty(value: String): String {
......@@ -9,6 +13,26 @@ fun String.ifEmpty(value: String): String {
return this
}
var TextView.content: String
get() = this.text.toString()
set(value) { this.text = value }
\ No newline at end of file
fun View.setVisibility(value: Boolean) {
if (value) {
this.visibility = View.VISIBLE
} else {
this.visibility = View.GONE
}
}
fun ViewGroup.inflate(@LayoutRes resource: Int): View {
return LayoutInflater.from(context).inflate(resource, this, false)
}
var TextView.textContent: String
get() = text.toString()
set(value) {
text = value
}
var TextView.hintContent: String
get() = hint.toString()
set(value) {
text = value
}
......@@ -3,7 +3,8 @@ package chat.rocket.android.util
import chat.rocket.common.util.PlatformLogger
import timber.log.Timber
class TimberLogger : PlatformLogger {
object TimberLogger : PlatformLogger {
override fun debug(s: String) {
Timber.d(s)
}
......
package chat.rocket.android.webview
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
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): Intent {
return Intent(this, WebViewActivity::class.java).apply {
putExtra(INTENT_WEB_PAGE_URL, webPageUrl)
}
}
private const val INTENT_WEB_PAGE_URL = "web_page_url"
class WebViewActivity : AppCompatActivity() {
private lateinit var webPageUrl: 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" }
setupToolbar()
}
override fun onResume() {
super.onResume()
setupWebView()
}
override fun onBackPressed() {
if (web_view.canGoBack()) {
web_view.goBack()
} else {
finishActivity()
}
}
private fun setupToolbar() {
toolbar.title = getString(R.string.title_legal_terms)
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp)
toolbar.setNavigationOnClickListener {
finishActivity()
}
}
@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
web_view.settings.javaScriptEnabled = true
web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view_loading.hide()
}
}
web_view.loadUrl(webPageUrl)
}
private fun finishActivity() {
super.onBackPressed()
overridePendingTransition(R.anim.hold, R.anim.slide_down)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-100%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="800"
android:fromYDelta="0.0%p"
android:toYDelta="0.0%p" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="350"
android:fromYDelta="0.0%"
android:toYDelta="100.0%" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="350"
android:fromYDelta="100.0%"
android:toYDelta="0.0%" />
</set>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
<?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=".webview.WebViewActivity">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar" />
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/layout_app_bar" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator" />
</RelativeLayout>
\ No newline at end of file
......@@ -5,8 +5,8 @@
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:fillViewport="true">
android:fillViewport="true"
tools:context=".authentication.login.ui.LoginFragment">
<android.support.constraint.ConstraintLayout
android:id="@+id/middle_container"
......@@ -45,14 +45,40 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username_or_email" />
<TextView
android:id="@+id/text_new_to_rocket_chat"
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="16dp"
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_password"
tools:visibility="visible" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
app:indicatorName="BallPulseIndicator"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/social_accounts_container"
android:layout_width="match_parent"
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="16dp"
android:animateLayoutChanges="true"
android:layout_marginTop="20dp"
android:background="@color/colorPrimaryDark"
android:gravity="center"
android:orientation="vertical"
......@@ -61,7 +87,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/text_new_to_rocket_chat"
tools:visibility="visible">
<TextView
......@@ -165,23 +191,6 @@
app:layout_constraintTop_toBottomOf="@+id/social_accounts_container"
tools:visibility="visible" />
<TextView
android:id="@+id/text_new_to_rocket_chat"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
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:gravity="center"
android:text="@string/msg_new_to_rocket_chat"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/social_accounts_container"
tools:visibility="visible" />
<Button
android:id="@+id/button_log_in"
style="@style/AuthenticationButton"
......
<?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:id="@+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:focusableInTouchMode="true"
tools:context=".authentication.server.ui.ServerFragment">
<TextView
android:id="@+id/text_headline"
......@@ -10,7 +15,7 @@
android:text="@string/title_sign_in_your_server" />
<TextView
android:id="@+id/server_protocol_label"
android:id="@+id/text_server_protocol"
style="@style/AuthenticationLabel"
android:layout_below="@id/text_headline"
android:layout_marginTop="32dp"
......@@ -23,12 +28,23 @@
android:layout_below="@id/text_headline"
android:layout_marginStart="0dp"
android:layout_marginTop="32dp"
android:layout_toEndOf="@id/server_protocol_label"
android:layout_toEndOf="@id/text_server_protocol"
android:cursorVisible="false"
android:hint="@string/default_server"
android:imeOptions="actionDone"
android:inputType="textUri"
android:paddingEnd="0dp"
android:paddingStart="0dp" />
<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_connect"
style="@style/AuthenticationButton"
......
<?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/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".authentication.signup.ui.SignupFragment">
<TextView
android:id="@+id/text_headline"
......@@ -61,6 +63,19 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="16dp"
android:visibility="gone"
app:indicatorName="BallPulseIndicator"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_email"
tools:visibility="visible" />
<TextView
android:id="@+id/text_new_user_agreement"
android:layout_width="wrap_content"
......@@ -69,7 +84,7 @@
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:gravity="center"
android:text="@string/msg_new_user_agreement"
android:textColorLink="@color/colorAccent"
app:layout_constraintBottom_toTopOf="@+id/button_sign_up"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
......
<?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">
android:layout_height="match_parent"
tools:context=".authentication.twofactor.ui.TwoFAFragment">
<TextView
android:id="@+id/text_headline"
......@@ -19,6 +22,16 @@
android:imeOptions="actionDone"
android:inputType="text" />
<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_log_in"
style="@style/AuthenticationButton"
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Titles -->
<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_legal_terms">Termos Legais</string>
<!-- Actions -->
<string name="action_connect">Conectar</string>
<string name="action_send">Enviar</string>
<string name="action_terms_of_service">Termos de Serviço</string>
<string name="action_privacy_policy">Política de Privacidade</string>
<!-- Regular information messages -->
<string name="msg_no_internet_connection">Sem conexão à internet</string>
<string name="msg_generic_error">Desculpe, ocorreu um erro, tente novamente</string>
<string name="msg_username">nome de usuário</string>
<string name="msg_username_or_email">nome de usuário ou email</string>
<string name="msg_password">senha</string>
<string name="msg_name_and_surname">nome e sobrenome</string>
<string name="msg_email">email</string>
<string name="msg_or_continue_using_social_accounts">Ou continue através de contas sociais</string>
<string name="msg_new_to_rocket_chat">Novo no Rocket Chat? <font color='#FF1976D2'>Inscreva-se</font></string>
<string name="msg_new_user_agreement">Ao proceder você concorda com nossos\n<font color='#FF1976D2'>Termos de Serviço</font> e <font color='#FF1976D2'>Política de Privacidade</font></string>
<string name="msg_new_to_rocket_chat">Novo no Rocket Chat? %1$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_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_yesterday">ontem</string>
<string name="msg_message">Messagem</string>
<string name="msg_content_description_log_in_using_facebook">Fazer login através do Facebook</string>
......
......@@ -5,21 +5,27 @@
<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_legal_terms">Legal Terms</string>
<!-- Actions -->
<string name="action_connect">Connect</string>
<string name="action_send">Send</string>
<string name="action_terms_of_service">Terms of Service</string>
<string name="action_privacy_policy">Privacy Policy</string>
<!-- Regular information messages -->
<string name="msg_no_internet_connection">No internet connection</string>
<string name="msg_generic_error">Sorry, an error has occurred, please try again</string>
<string name="msg_username">username</string>
<string name="msg_username_or_email">username or email</string>
<string name="msg_password">password</string>
<string name="msg_name_and_surname">name and surname</string>
<string name="msg_email">email</string>
<string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string>
<string name="msg_new_to_rocket_chat">New to Rocket Chat? <font color='#FF1976D2'>Sign up</font></string>
<string name="msg_new_user_agreement">By proceeding you are agreeing to our\n<font color='#FF1976D2'>Terms of Service</font> and <font color='#FF1976D2'>Privacy Policy</font></string>
<string name="msg_new_to_rocket_chat">New to Rocket Chat? %1$s</string>
<string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string>
<string name="msg_2fa_code">2FA Code</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string>
<string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string>
<string name="msg_yesterday">Yesterday</string>
<string name="msg_message">Message</string>
......
......@@ -10,7 +10,7 @@
</style>
<style name="AuthenticationTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:navigationBarColor">@color/colorPrimary</item>
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>
<style name="ChatListTheme" parent="AppTheme">
......
......@@ -12,71 +12,75 @@ ext {
kotshi : '0.3.0-beta2',
// Main dependencies
support : '27.0.2',
constraintLayout : '1.0.2',
dagger : '2.13',
exoPlayer : '2.6.0',
room : '1.0.0',
rxjava : '2.1.4',
rxandroid : '2.0.1',
moshi : '1.6.0-SNAPSHOT',
okhttp : '3.9.0',
timber : '4.5.1',
threeTenABP : '1.0.5',
fresco : '1.5.0',
frescoImageViewer : '0.5.0',
floatingSearchView: '2.1.1',
androidSvg : '1.2.1',
support : '27.0.2',
constraintLayout : '1.0.2',
dagger : '2.13',
exoPlayer : '2.6.0',
room : '1.0.0',
rxjava : '2.1.4',
rxandroid : '2.0.1',
moshi : '1.6.0-SNAPSHOT',
okhttp : '3.9.0',
timber : '4.5.1',
threeTenABP : '1.0.5',
fresco : '1.5.0',
frescoImageViewer : '0.5.0',
floatingSearchView : '2.1.1',
androidSvg : '1.2.1',
aVLoadingIndicatorView : '2.1.3',
// For testing
junit : '4.12',
truth : '0.36',
expresso : '3.0.1',
mockito : '2.10.0'
junit : '4.12',
truth : '0.36',
expresso : '3.0.1',
mockito : '2.10.0'
]
libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}",
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}",
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
appCompat : "com.android.support:appcompat-v7:${versions.support}",
annotations : "com.android.support:support-annotations:${versions.support}",
recyclerview : "com.android.support:recyclerview-v7:${versions.support}",
design : "com.android.support:design:${versions.support}",
constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}",
cardView : "com.android.support:cardview-v7:${versions.support}",
appCompat : "com.android.support:appcompat-v7:${versions.support}",
annotations : "com.android.support:support-annotations:${versions.support}",
recyclerview : "com.android.support:recyclerview-v7:${versions.support}",
design : "com.android.support:design:${versions.support}",
constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}",
cardView : "com.android.support:cardview-v7:${versions.support}",
dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
room : "android.arch.persistence.room:runtime:${versions.room}",
roomProcessor : "android.arch.persistence.room:compiler:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}",
room : "android.arch.persistence.room:runtime:${versions.room}",
roomProcessor : "android.arch.persistence.room:compiler:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}",
rxjava : "io.reactivex.rxjava2:rxjava:${versions.rxjava}",
rxandroid : "io.reactivex.rxjava2:rxandroid:${versions.rxandroid}",
rxjava : "io.reactivex.rxjava2:rxjava:${versions.rxjava}",
rxandroid : "io.reactivex.rxjava2:rxandroid:${versions.rxandroid}",
moshi : "com.squareup.moshi:moshi:${versions.moshi}",
okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}",
moshi : "com.squareup.moshi:moshi:${versions.moshi}",
moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshi}",
okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}",
timber : "com.jakewharton.timber:timber:${versions.timber}",
threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}",
timber : "com.jakewharton.timber:timber:${versions.timber}",
threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}",
frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}",
frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}",
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
// For testing
toolsJar : files(Jvm.current().getToolsJar()),
......
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