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 { ...@@ -76,6 +76,8 @@ dependencies {
implementation libraries.androidSvg implementation libraries.androidSvg
implementation libraries.aVLoadingIndicatorView
testImplementation libraries.junit testImplementation libraries.junit
androidTestImplementation (libraries.expressoCore , { androidTestImplementation (libraries.expressoCore , {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<application <application
android:name=".app.RocketChatApplication" android:name=".app.RocketChatApplication"
...@@ -18,7 +19,8 @@ ...@@ -18,7 +19,8 @@
android:name=".authentication.ui.AuthenticationActivity" android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation" android:configChanges="orientation"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/AuthenticationTheme"> android:theme="@style/AuthenticationTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
...@@ -33,8 +35,11 @@ ...@@ -33,8 +35,11 @@
<activity <activity
android:name=".app.ChatRoomActivity" android:name=".app.ChatRoomActivity"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme" />
</activity>
<activity
android:name=".webview.WebViewActivity"
android:theme="@style/AppTheme" />
</application> </application>
</manifest> </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 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.presentation.AuthenticationNavigator
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
...@@ -13,13 +13,7 @@ class AuthenticationModule { ...@@ -13,13 +13,7 @@ class AuthenticationModule {
@Provides @Provides
@PerActivity @PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity) = AuthenticationNavigator(activity) fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context)
@Provides
@PerActivity
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
@Provides @Provides
fun provideJob(): Job { fun provideJob(): Job {
......
...@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job ...@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment @PerFragment
class LoginFragmentModule { class LoginFragmentModule {
@Provides @Provides
fun loginView(frag: LoginFragment): LoginView { fun loginView(frag: LoginFragment): LoginView {
return frag return frag
...@@ -26,4 +27,4 @@ class LoginFragmentModule { ...@@ -26,4 +27,4 @@ class LoginFragmentModule {
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
} }
\ No newline at end of file
package chat.rocket.android.authentication.login.presentation 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.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject import javax.inject.Inject
class LoginPresenter @Inject constructor(private val view: LoginView, class LoginPresenter @Inject constructor(private val view: LoginView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator) {
private val okHttpClient: OkHttpClient, @Inject lateinit var client: RocketChatClient
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
val client: RocketChatClient = RocketChatClient.create { fun authenticate(usernameOrEmail: String, password: String) {
httpClient = okHttpClient when {
restUrl = HttpUrl.parse(navigator.currentServer)!! usernameOrEmail.isBlank() -> {
websocketUrl = navigator.currentServer!! view.alertWrongUsernameOrEmail()
tokenRepository = repository }
platformLogger = logger password.isEmpty() -> {
} view.alertWrongPassword()
}
fun authenticate(username: String, password: String) { else -> {
// TODO - validate input launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
launchUI(strategy) { try {
view.showLoading() client.login(usernameOrEmail, password) // TODO This function returns a user token so should we save it?
try { navigator.toChatList()
val token = client.login(username, password) } 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() view.hideLoading()
} catch (ex: RocketChatException) { } else {
when(ex) { view.showNoInternetConnection()
is RocketChatTwoFactorException -> }
navigator.toTwoFA(navigator.currentServer!!, username, password)
else ->
view.onLoginError(ex.message)
} }
} finally {
view.hideLoading()
} }
} }
} }
fun signup() { fun signup() {
navigator.toSignUp(navigator.currentServer!!) navigator.toSignUp()
} }
} }
\ No newline at end of file
package chat.rocket.android.authentication.login.presentation 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.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface LoginView : LoadingView { interface LoginView : LoadingView, MessageView, InternetView {
fun onLoginError(message: String?)
/**
* 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.presentation package chat.rocket.android.authentication.presentation
import android.content.Context
import android.content.Intent import android.content.Intent
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.app.MainActivity 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.login.ui.LoginFragment
import chat.rocket.android.authentication.signup.ui.SignupFragment import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.util.addFragmentBackStack import chat.rocket.android.util.addFragmentBackStack
import chat.rocket.android.webview.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity) { class AuthenticationNavigator(internal val activity: AuthenticationActivity, internal val context: Context) {
var currentServer: String? = null lateinit var server: String
lateinit var usernameOrEmail: String
lateinit var password: String
fun toLogin(server: String) { fun toLogin(server: String) {
currentServer = server this.server = server
activity.addFragmentBackStack("loginFragment", R.id.fragment_container) { activity.addFragmentBackStack("loginFragment", R.id.fragment_container) {
LoginFragment.newInstance(server) LoginFragment.newInstance()
} }
} }
fun toChatList() { fun toTwoFA(usernameOrEmail: String, password: String) {
val chatRoom = Intent(activity, MainActivity::class.java).apply { this.usernameOrEmail = usernameOrEmail
//TODO any parameter to pass 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) { fun toSignUp() {
currentServer = server activity.addFragmentBackStack("signupFragment", R.id.fragment_container) {
activity.addFragmentBackStack("twoFAFragment", R.id.fragment_container) { SignupFragment.newInstance()
TwoFAFragment.newInstance(server, username, password)
} }
} }
fun toSignUp(server: String) { fun toTermsOfService() {
currentServer = server val webPageUrl = server + "/terms-of-service" // TODO Move to UrlHelper
activity.addFragmentBackStack("signupFragment", R.id.fragment_container) { activity.startActivity(context.webViewIntent(webPageUrl))
SignupFragment.newInstance(server) 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 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.presentation.ServerView
import chat.rocket.android.authentication.server.ui.ServerFragment import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module @Module
class ServerFragmentModule { class ServerFragmentModule {
@Provides @Provides
fun serverView(frag: ServerFragment): ServerView { fun serverView(frag: ServerFragment): ServerView {
return frag 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 package chat.rocket.android.authentication.server.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator 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 import javax.inject.Inject
class ServerPresenter @Inject constructor(private val view: ServerView, class ServerPresenter @Inject constructor(private val view: ServerView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator) { private val navigator: AuthenticationNavigator) {
@Inject lateinit var client: RocketChatClient
fun login(server: String) { fun connect(server: String) {
// TODO - validate server URL and get server settings and info before going to Login screen launchUI(strategy) {
navigator.toLogin(server) 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 package chat.rocket.android.authentication.server.presentation
interface ServerView import chat.rocket.android.core.behaviours.InternetView
\ No newline at end of file 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 ...@@ -5,44 +5,70 @@ import android.support.v4.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.R
import chat.rocket.android.authentication.server.presentation.ServerPresenter import chat.rocket.android.authentication.server.presentation.ServerPresenter
import chat.rocket.android.authentication.server.presentation.ServerView 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 dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_server.* import kotlinx.android.synthetic.main.fragment_authentication_server.*
import javax.inject.Inject import javax.inject.Inject
class ServerFragment : Fragment(), ServerView { class ServerFragment : Fragment(), ServerView {
@Inject lateinit var presenter: ServerPresenter
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
text_server_url.isCursorVisible = KeyboardHelper.isSoftKeyboardShown(relative_layout.rootView)
}
@Inject companion object {
lateinit var presenter: ServerPresenter fun newInstance() = ServerFragment()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
} }
override fun onCreateView(inflater: LayoutInflater, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_authentication_server)
container: ViewGroup?,
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_authentication_server, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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 { override fun hideLoading() {
val url = text_server_url.text.toString().ifEmpty(text_server_url.hint.toString()) view_loading.setVisibility(false)
presenter.login(server_protocol_label.text.toString() + url) enableUserInput(true)
}
} }
companion object { override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
fun newInstance() = ServerFragment()
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 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.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.signup import chat.rocket.core.internal.rest.signup
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class SignupPresenter @Inject constructor(private val view: SignupView, class SignupPresenter @Inject constructor(private val view: SignupView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator) {
private val okHttpClient: OkHttpClient, @Inject lateinit var client: RocketChatClient
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
val client: RocketChatClient = RocketChatClient.create { fun signup(name: String, username: String, password: String, email: String) {
httpClient = okHttpClient when {
restUrl = HttpUrl.parse(navigator.currentServer)!! name.isBlank() -> {
websocketUrl = navigator.currentServer!! view.alertBlankName()
tokenRepository = repository }
platformLogger = logger username.isBlank() -> {
} view.alertBlankUsername()
}
fun signup(email: String, name: String, username: String, password: String) { password.isEmpty() -> {
// TODO - validate input view.alertEmptyPassword()
}
launchUI(strategy) { email.isBlank() -> {
view.showLoading() view.alertBlankEmail()
}
try { else -> {
val user = client.signup(email, name, username, password) launchUI(strategy) {
Timber.d("Created user: $user") if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
val token = client.login(username, password) try {
Timber.d("Logged in: $token") 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() view.hideLoading()
} catch (ex: RocketChatException) { } else {
view.onSignupError(ex.message) view.showNoInternetConnection()
} finally { }
view.hideLoading() }
} }
} }
} }
fun termsOfService() {
navigator.toTermsOfService()
}
fun privacyPolicy() {
navigator.toPrivacyPolicy()
}
} }
\ No newline at end of file
package chat.rocket.android.authentication.signup.presentation 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.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface SignupView : LoadingView { interface SignupView : LoadingView, MessageView, InternetView {
fun onSignupError(message: String? = "Unknown error")
/**
* 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 package chat.rocket.android.authentication.signup.ui
import DrawableHelper import DrawableHelper
import android.app.ProgressDialog import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.text.style.ClickableSpan
import android.view.* import android.view.*
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R 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.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView 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 dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.* import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import javax.inject.Inject import javax.inject.Inject
class SignupFragment : Fragment(), SignupView { class SignupFragment : Fragment(), SignupView {
@Inject lateinit var presenter: SignupPresenter
@Inject lateinit var appContext: Context
companion object { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
private const val SERVER_URL = "server_url" if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.setVisibility(false)
fun newInstance(url: String) = SignupFragment().apply { } else {
arguments = Bundle(1).apply { text_new_user_agreement.setVisibility(true)
putString(SERVER_URL, url)
}
} }
} }
@Inject companion object {
lateinit var presenter: SignupPresenter fun newInstance() = SignupFragment()
var progress: ProgressDialog? = null }
lateinit var serverUrl: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
// TODO - research a better way to initialize parameters on fragments.
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_sign_up, container, false) 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 { ...@@ -52,65 +52,105 @@ class SignupFragment : Fragment(), SignupView {
tintEditTextDrawableStart() tintEditTextDrawableStart()
} }
setupGlobalLayoutListener() constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
button_sign_up.setOnClickListener { setUpNewUserAgreementListener()
val email = text_email.content
val name = text_name.content
val username = text_username.content
val password = text_password.content
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() { override fun onDestroyView() {
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
super.onDestroyView() super.onDestroyView()
constraint_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
} }
private fun tintEditTextDrawableStart() { override fun alertBlankName() {
activity?.applicationContext?.apply { AnimationHelper.vibrateSmartPhone(appContext)
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, this) AnimationHelper.shakeView(text_name)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this) text_name.requestFocus()
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 setupGlobalLayoutListener() { override fun alertBlankUsername() {
constraint_layout.viewTreeObserver.addOnGlobalLayoutListener(layoutListener) AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_username)
text_username.requestFocus()
} }
val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { override fun alertEmptyPassword() {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) { AnimationHelper.vibrateSmartPhone(appContext)
text_new_user_agreement.visibility = View.GONE AnimationHelper.shakeView(text_password)
} else { text_password.requestFocus()
text_new_user_agreement.visibility = View.VISIBLE }
}
override fun alertBlankEmail() {
AnimationHelper.vibrateSmartPhone(appContext)
AnimationHelper.shakeView(text_email)
text_email.requestFocus()
} }
override fun showLoading() { override fun showLoading() {
// TODO - change for a proper progress indicator enableUserInput(false)
progress = ProgressDialog.show(activity, "Authenticating", view_loading.setVisibility(true)
"Registering user", true, true)
} }
override fun hideLoading() { override fun hideLoading() {
progress?.apply { view_loading.setVisibility(false)
cancel() enableUserInput(true)
}
progress = null
} }
override fun onSignupError(message: String?) { override fun showMessage(message: String) {
// TODO - show a proper error message Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
Toast.makeText(activity, message, Toast.LENGTH_LONG).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 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.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject import javax.inject.Inject
class TwoFAPresenter @Inject constructor(private val view: TwoFAView, class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator) {
private val okHttpClient: OkHttpClient, @Inject lateinit var client: RocketChatClient
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
val client: RocketChatClient = RocketChatClient.create { fun authenticate(twoFactorAuthenticationCode: String) {
httpClient = okHttpClient if (twoFactorAuthenticationCode.isBlank()) {
restUrl = HttpUrl.parse(navigator.currentServer)!! view.alertBlankTwoFactorAuthenticationCode()
websocketUrl = navigator.currentServer!! } else {
tokenRepository = repository launchUI(strategy) {
platformLogger = logger if (NetworkHelper.hasInternetAccess()) {
} view.showLoading()
fun authenticate(username: String, password: String, pin: String) {
// TODO - validate input
launchUI(strategy) { try {
view.showLoading() client.login(navigator.usernameOrEmail, navigator.password, twoFactorAuthenticationCode) // TODO This function returns a user token so should we save it?
try { navigator.toChatList()
val token = client.login(username, password, pin) } 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() view.hideLoading()
} catch (ex: RocketChatException) { } else {
view.onLoginError(ex.message) view.showNoInternetConnection()
} finally { }
view.hideLoading()
} }
} }
} }
fun signup() { fun signup() {
navigator.toSignUp(navigator.currentServer!!) navigator.toSignUp()
} }
} }
\ No newline at end of file
package chat.rocket.android.authentication.twofactor.presentation 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 interface TwoFAView : LoadingView, MessageView, InternetView {
\ No newline at end of file
/**
* 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 package chat.rocket.android.authentication.twofactor.ui
import DrawableHelper import DrawableHelper
import android.app.ProgressDialog import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView 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 dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.* import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import javax.inject.Inject import javax.inject.Inject
class TwoFAFragment : Fragment(), TwoFAView { class TwoFAFragment : Fragment(), TwoFAView {
@Inject lateinit var presenter: TwoFAPresenter
@Inject lateinit var appContext: Context // TODO we really need it? Check alternatives...
companion object { companion object {
private const val SERVER_URL = "server_url" fun newInstance() = TwoFAFragment()
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)
}
}
} }
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?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
// 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) ?: ""
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_two_fa, container, false) 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 { ...@@ -57,42 +38,58 @@ class TwoFAFragment : Fragment(), TwoFAView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart() tintEditTextDrawableStart()
} }
setupOnClickListener()
}
button_log_in.setOnClickListener { override fun alertBlankTwoFactorAuthenticationCode() {
presenter.authenticate(username, password, text_two_factor_auth.content) 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() { private fun tintEditTextDrawableStart() {
activity?.applicationContext?.apply { activity?.apply {
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, this) val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, this)
DrawableHelper.wrapDrawable(lockDrawable) DrawableHelper.wrapDrawable(lockDrawable)
DrawableHelper.tintDrawable(lockDrawable, this, R.color.colorDrawableTintGrey) DrawableHelper.tintDrawable(lockDrawable, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_two_factor_auth, lockDrawable) DrawableHelper.compoundDrawable(text_two_factor_auth, lockDrawable)
} }
} }
override fun showLoading() { private fun enableUserInput(value: Boolean) {
// TODO - change for a proper progress indicator button_log_in.isEnabled = value
progress = ProgressDialog.show(activity, "Authenticating", text_two_factor_auth.isEnabled = value
"Verifying user credentials", true, true)
} }
override fun hideLoading() { private fun setupOnClickListener() {
progress?.apply { button_log_in.setOnClickListener {
cancel() 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 ...@@ -4,7 +4,6 @@ import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.app.LayoutHelper
import chat.rocket.android.authentication.server.ui.ServerFragment import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.util.addFragment import chat.rocket.android.util.addFragment
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
...@@ -14,29 +13,19 @@ import dagger.android.support.HasSupportFragmentInjector ...@@ -14,29 +13,19 @@ import dagger.android.support.HasSupportFragmentInjector
import javax.inject.Inject import javax.inject.Inject
class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
val layoutHelper = LayoutHelper()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidInjection.inject(this)
setContentView(R.layout.activity_authentication) setContentView(R.layout.activity_authentication)
layoutHelper.install(this) AndroidInjection.inject(this)
addFragment("authenticationServerFragment", R.id.fragment_container) { addFragment("authenticationServerFragment", R.id.fragment_container) {
ServerFragment.newInstance() ServerFragment.newInstance()
} }
} }
override fun onDestroy() {
layoutHelper.remove()
super.onDestroy()
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> { override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector return fragmentDispatchingAndroidInjector
} }
......
package chat.rocket.android.core.behaviours
interface InternetView {
fun showNoInternetConnection()
}
\ No newline at end of file
package chat.rocket.android.core.behaviours package chat.rocket.android.core.behaviours
interface LoadingView { interface LoadingView {
fun showLoading() fun showLoading()
fun hideLoading() 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 ...@@ -8,6 +8,7 @@ import kotlinx.coroutines.experimental.Job
import javax.inject.Inject import javax.inject.Inject
class CancelStrategy @Inject constructor(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver { class CancelStrategy @Inject constructor(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver {
init { init {
owner.lifecycle.addObserver(this) owner.lifecycle.addObserver(this)
} }
......
...@@ -10,7 +10,7 @@ import dagger.android.support.AndroidSupportInjectionModule ...@@ -10,7 +10,7 @@ import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Component(modules = arrayOf(AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class)) @Component(modules = [AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class])
interface AppComponent { interface AppComponent {
@Component.Builder @Component.Builder
......
...@@ -11,19 +11,17 @@ import chat.rocket.android.dagger.scope.PerActivity ...@@ -11,19 +11,17 @@ import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
@Module @Module abstract class ActivityBuilder {
abstract class ActivityBuilder {
@PerActivity @PerActivity
@ContributesAndroidInjector(modules = arrayOf( @ContributesAndroidInjector(modules = [AuthenticationModule::class,
AuthenticationModule::class,
LoginFragmentProvider::class, LoginFragmentProvider::class,
ServerFragmentProvider::class, ServerFragmentProvider::class,
SignupFragmentProvider::class, SignupFragmentProvider::class,
TwoFAFragmentProvider::class TwoFAFragmentProvider::class
)) ])
abstract fun bindAuthenticationActivity(): AuthenticationActivity abstract fun bindAuthenticationActivity(): AuthenticationActivity
@ContributesAndroidInjector @ContributesAndroidInjector abstract fun bindMainActivity(): MainActivity
abstract fun bindMainActivity(): MainActivity
} }
...@@ -5,11 +5,14 @@ import android.arch.persistence.room.Room ...@@ -5,11 +5,14 @@ import android.arch.persistence.room.Room
import android.content.Context import android.content.Context
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.app.RocketChatDatabase import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.server.infraestructure.ServerDao import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.util.TimberLogger import chat.rocket.android.util.TimberLogger
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton import javax.inject.Singleton
...@@ -17,6 +20,20 @@ import javax.inject.Singleton ...@@ -17,6 +20,20 @@ import javax.inject.Singleton
@Module @Module
class AppModule { 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 @Provides
@Singleton @Singleton
fun provideRocketChatDatabase(context: Application): RocketChatDatabase { fun provideRocketChatDatabase(context: Application): RocketChatDatabase {
...@@ -35,20 +52,6 @@ class AppModule { ...@@ -35,20 +52,6 @@ class AppModule {
return database.serverDao() 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 @Provides
@Singleton @Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
...@@ -70,9 +73,15 @@ class AppModule { ...@@ -70,9 +73,15 @@ class AppModule {
}.build() }.build()
} }
@Provides
@Singleton
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
@Provides @Provides
@Singleton @Singleton
fun providePlatformLogger(): PlatformLogger { 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.graphics.Rect
import android.view.View 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 ...@@ -2,6 +2,7 @@ package chat.rocket.android.util
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) { fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance() val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
...@@ -10,5 +11,9 @@ fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> ...@@ -10,5 +11,9 @@ fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () ->
fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, newInstance: () -> Fragment) { fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance() 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 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 import android.widget.TextView
fun String.ifEmpty(value: String): String { fun String.ifEmpty(value: String): String {
...@@ -9,6 +13,26 @@ fun String.ifEmpty(value: String): String { ...@@ -9,6 +13,26 @@ fun String.ifEmpty(value: String): String {
return this return this
} }
var TextView.content: String fun View.setVisibility(value: Boolean) {
get() = this.text.toString() if (value) {
set(value) { this.text = value } this.visibility = View.VISIBLE
\ No newline at end of file } 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 ...@@ -3,7 +3,8 @@ package chat.rocket.android.util
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import timber.log.Timber import timber.log.Timber
class TimberLogger : PlatformLogger { object TimberLogger : PlatformLogger {
override fun debug(s: String) { override fun debug(s: String) {
Timber.d(s) 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 @@ ...@@ -5,8 +5,8 @@
android:id="@+id/scroll_view" android:id="@+id/scroll_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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.support.constraint.ConstraintLayout
android:id="@+id/middle_container" android:id="@+id/middle_container"
...@@ -45,14 +45,40 @@ ...@@ -45,14 +45,40 @@
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username_or_email" /> 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 <LinearLayout
android:id="@+id/social_accounts_container" android:id="@+id/social_accounts_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins" android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins" android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp" android:layout_marginTop="20dp"
android:animateLayoutChanges="true"
android:background="@color/colorPrimaryDark" android:background="@color/colorPrimaryDark"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
...@@ -61,7 +87,7 @@ ...@@ -61,7 +87,7 @@
android:visibility="gone" android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password" app:layout_constraintTop_toBottomOf="@+id/text_new_to_rocket_chat"
tools:visibility="visible"> tools:visibility="visible">
<TextView <TextView
...@@ -165,23 +191,6 @@ ...@@ -165,23 +191,6 @@
app:layout_constraintTop_toBottomOf="@+id/social_accounts_container" app:layout_constraintTop_toBottomOf="@+id/social_accounts_container"
tools:visibility="visible" /> 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 <Button
android:id="@+id/button_log_in" android:id="@+id/button_log_in"
style="@style/AuthenticationButton" style="@style/AuthenticationButton"
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:focusableInTouchMode="true"
tools:context=".authentication.server.ui.ServerFragment">
<TextView <TextView
android:id="@+id/text_headline" android:id="@+id/text_headline"
...@@ -10,7 +15,7 @@ ...@@ -10,7 +15,7 @@
android:text="@string/title_sign_in_your_server" /> android:text="@string/title_sign_in_your_server" />
<TextView <TextView
android:id="@+id/server_protocol_label" android:id="@+id/text_server_protocol"
style="@style/AuthenticationLabel" style="@style/AuthenticationLabel"
android:layout_below="@id/text_headline" android:layout_below="@id/text_headline"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
...@@ -23,12 +28,23 @@ ...@@ -23,12 +28,23 @@
android:layout_below="@id/text_headline" android:layout_below="@id/text_headline"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginTop="32dp" 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:hint="@string/default_server"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="textUri" android:inputType="textUri"
android:paddingEnd="0dp"
android:paddingStart="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 <Button
android:id="@+id/button_connect" android:id="@+id/button_connect"
style="@style/AuthenticationButton" style="@style/AuthenticationButton"
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_layout" android:id="@+id/constraint_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:context=".authentication.signup.ui.SignupFragment">
<TextView <TextView
android:id="@+id/text_headline" android:id="@+id/text_headline"
...@@ -61,6 +63,19 @@ ...@@ -61,6 +63,19 @@
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_password" /> 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 <TextView
android:id="@+id/text_new_user_agreement" android:id="@+id/text_new_user_agreement"
android:layout_width="wrap_content" android:layout_width="wrap_content"
...@@ -69,7 +84,7 @@ ...@@ -69,7 +84,7 @@
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins" android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins" android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:gravity="center" android:gravity="center"
android:text="@string/msg_new_user_agreement" android:textColorLink="@color/colorAccent"
app:layout_constraintBottom_toTopOf="@+id/button_sign_up" app:layout_constraintBottom_toTopOf="@+id/button_sign_up"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" /> app:layout_constraintRight_toRightOf="parent" />
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:context=".authentication.twofactor.ui.TwoFAFragment">
<TextView <TextView
android:id="@+id/text_headline" android:id="@+id/text_headline"
...@@ -19,6 +22,16 @@ ...@@ -19,6 +22,16 @@
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="text" /> 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 <Button
android:id="@+id/button_log_in" android:id="@+id/button_log_in"
style="@style/AuthenticationButton" style="@style/AuthenticationButton"
......
<?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Titles --> <!-- Titles -->
<string name="title_sign_in_your_server">Faça login no seu servidor</string> <string name="title_sign_in_your_server">Faça login no seu servidor</string>
<string name="title_log_in">Entrar</string> <string name="title_log_in">Entrar</string>
<string name="title_sign_up">Inscreva-se</string> <string name="title_sign_up">Inscreva-se</string>
<string name="title_legal_terms">Termos Legais</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Conectar</string> <string name="action_connect">Conectar</string>
<string name="action_send">Enviar</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 --> <!-- 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">nome de usuário</string>
<string name="msg_username_or_email">nome de usuário ou email</string> <string name="msg_username_or_email">nome de usuário ou email</string>
<string name="msg_password">senha</string> <string name="msg_password">senha</string>
<string name="msg_name_and_surname">nome e sobrenome</string> <string name="msg_name_and_surname">nome e sobrenome</string>
<string name="msg_email">email</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_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_to_rocket_chat">Novo no Rocket Chat? %1$s</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_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string>
<string name="msg_2fa_code">Código 2FA</string> <string name="msg_2fa_code">Código 2FA</string>
<string name="msg_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_yesterday">ontem</string> <string name="msg_yesterday">ontem</string>
<string name="msg_message">Messagem</string> <string name="msg_message">Messagem</string>
<string name="msg_content_description_log_in_using_facebook">Fazer login através do Facebook</string> <string name="msg_content_description_log_in_using_facebook">Fazer login através do Facebook</string>
......
...@@ -5,21 +5,27 @@ ...@@ -5,21 +5,27 @@
<string name="title_sign_in_your_server">Sign in your server</string> <string name="title_sign_in_your_server">Sign in your server</string>
<string name="title_log_in">Log in</string> <string name="title_log_in">Log in</string>
<string name="title_sign_up">Sign up</string> <string name="title_sign_up">Sign up</string>
<string name="title_legal_terms">Legal Terms</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Connect</string> <string name="action_connect">Connect</string>
<string name="action_send">Send</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 --> <!-- 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">username</string>
<string name="msg_username_or_email">username or email</string> <string name="msg_username_or_email">username or email</string>
<string name="msg_password">password</string> <string name="msg_password">password</string>
<string name="msg_name_and_surname">name and surname</string> <string name="msg_name_and_surname">name and surname</string>
<string name="msg_email">email</string> <string name="msg_email">email</string>
<string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string> <string name="msg_or_continue_using_social_accounts">Or continue using social accounts</string>
<string name="msg_new_to_rocket_chat">New to Rocket Chat? <font color='#FF1976D2'>Sign up</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<font color='#FF1976D2'>Terms of Service</font> and <font color='#FF1976D2'>Privacy Policy</font></string> <string name="msg_new_user_agreement">By proceeding you are agreeing to our\n%1$s and %2$s</string>
<string name="msg_2fa_code">2FA Code</string> <string name="msg_2fa_code">2FA Code</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string>
<string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string> <string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string>
<string name="msg_yesterday">Yesterday</string> <string name="msg_yesterday">Yesterday</string>
<string name="msg_message">Message</string> <string name="msg_message">Message</string>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</style> </style>
<style name="AuthenticationTheme" parent="Theme.AppCompat.NoActionBar"> <style name="AuthenticationTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:navigationBarColor">@color/colorPrimary</item> <item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style> </style>
<style name="ChatListTheme" parent="AppTheme"> <style name="ChatListTheme" parent="AppTheme">
......
...@@ -12,71 +12,75 @@ ext { ...@@ -12,71 +12,75 @@ ext {
kotshi : '0.3.0-beta2', kotshi : '0.3.0-beta2',
// Main dependencies // Main dependencies
support : '27.0.2', support : '27.0.2',
constraintLayout : '1.0.2', constraintLayout : '1.0.2',
dagger : '2.13', dagger : '2.13',
exoPlayer : '2.6.0', exoPlayer : '2.6.0',
room : '1.0.0', room : '1.0.0',
rxjava : '2.1.4', rxjava : '2.1.4',
rxandroid : '2.0.1', rxandroid : '2.0.1',
moshi : '1.6.0-SNAPSHOT', moshi : '1.6.0-SNAPSHOT',
okhttp : '3.9.0', okhttp : '3.9.0',
timber : '4.5.1', timber : '4.5.1',
threeTenABP : '1.0.5', threeTenABP : '1.0.5',
fresco : '1.5.0', fresco : '1.5.0',
frescoImageViewer : '0.5.0', frescoImageViewer : '0.5.0',
floatingSearchView: '2.1.1', floatingSearchView : '2.1.1',
androidSvg : '1.2.1', androidSvg : '1.2.1',
aVLoadingIndicatorView : '2.1.3',
// For testing // For testing
junit : '4.12', junit : '4.12',
truth : '0.36', truth : '0.36',
expresso : '3.0.1', expresso : '3.0.1',
mockito : '2.10.0' mockito : '2.10.0'
] ]
libraries = [ libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}", kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}",
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}", coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}", coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
appCompat : "com.android.support:appcompat-v7:${versions.support}", appCompat : "com.android.support:appcompat-v7:${versions.support}",
annotations : "com.android.support:support-annotations:${versions.support}", annotations : "com.android.support:support-annotations:${versions.support}",
recyclerview : "com.android.support:recyclerview-v7:${versions.support}", recyclerview : "com.android.support:recyclerview-v7:${versions.support}",
design : "com.android.support:design:${versions.support}", design : "com.android.support:design:${versions.support}",
constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}", constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}",
cardView : "com.android.support:cardview-v7:${versions.support}", cardView : "com.android.support:cardview-v7:${versions.support}",
dagger : "com.google.dagger:dagger:${versions.dagger}", dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}", daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}", daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}", daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}", exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
room : "android.arch.persistence.room:runtime:${versions.room}", room : "android.arch.persistence.room:runtime:${versions.room}",
roomProcessor : "android.arch.persistence.room:compiler:${versions.room}", roomProcessor : "android.arch.persistence.room:compiler:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}", roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}",
rxjava : "io.reactivex.rxjava2:rxjava:${versions.rxjava}", rxjava : "io.reactivex.rxjava2:rxjava:${versions.rxjava}",
rxandroid : "io.reactivex.rxjava2:rxandroid:${versions.rxandroid}", rxandroid : "io.reactivex.rxjava2:rxandroid:${versions.rxandroid}",
moshi : "com.squareup.moshi:moshi:${versions.moshi}", moshi : "com.squareup.moshi:moshi:${versions.moshi}",
okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}", moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshi}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}", okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}",
timber : "com.jakewharton.timber:timber:${versions.timber}", timber : "com.jakewharton.timber:timber:${versions.timber}",
threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}", threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}", fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}", frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}", frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${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 // For testing
toolsJar : files(Jvm.current().getToolsJar()), 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