Commit 0d5bbd04 authored by Lucio Maciel's avatar Lucio Maciel

Initial multi server support...

parent 2e7a0037
...@@ -35,6 +35,10 @@ ...@@ -35,6 +35,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".server.ui.ChangeServerActivity"
android:theme="@style/AuthenticationTheme" />
<activity <activity
android:name=".main.ui.MainActivity" android:name=".main.ui.MainActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
......
package chat.rocket.android.authentication.di package chat.rocket.android.authentication.di
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
...@@ -12,5 +11,5 @@ class AuthenticationModule { ...@@ -12,5 +11,5 @@ class AuthenticationModule {
@Provides @Provides
@PerActivity @PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context) fun provideAuthenticationNavigator(activity: AuthenticationActivity) = AuthenticationNavigator(activity)
} }
\ No newline at end of file
...@@ -6,7 +6,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -6,7 +6,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.generateRandomString import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.isEmailValid import chat.rocket.android.util.extensions.isEmailValid
...@@ -16,6 +18,7 @@ import chat.rocket.common.RocketChatTwoFactorException ...@@ -16,6 +18,7 @@ import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.* import chat.rocket.core.internal.rest.*
import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.delay
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
...@@ -27,9 +30,12 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -27,9 +30,12 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val settingsInteractor: GetSettingsInteractor, private val settingsInteractor: GetSettingsInteractor,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val saveAccountInteractor: SaveAccountInteractor,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory) {
// TODO - we should validate the current server when opening the app, and have a nonnull get() // TODO - we should validate the current server when opening the app, and have a nonnull get()
private val client: RocketChatClient = factory.create(serverInteractor.get()!!) private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun setupView() { fun setupView() {
val server = serverInteractor.get() val server = serverInteractor.get()
...@@ -125,7 +131,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -125,7 +131,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
} }
} }
saveToken(server, TokenModel(token.userId, token.authToken), client.me().username) val me = client.me()
saveToken(server, TokenModel(token.userId, token.authToken), me.username)
saveAccount(me)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
...@@ -163,7 +171,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -163,7 +171,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
if (server != null) { if (server != null) {
delay(3, TimeUnit.SECONDS) delay(3, TimeUnit.SECONDS)
val token = client.loginWithCas(casToken) val token = client.loginWithCas(casToken)
saveToken(server, TokenModel(token.userId, token.authToken), client.me().username) val me = client.me()
saveToken(server, TokenModel(token.userId, token.authToken), me.username)
saveAccount(me)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
} else { } else {
...@@ -199,4 +209,16 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -199,4 +209,16 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
} }
// TODO: Schedule push token registering when it comes up null // TODO: Schedule push token registering when it comes up null
} }
private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val thumb = UrlHelper.getAvatarUrl(currentServer, me.username!!)
val account = Account(currentServer, icon, logo, me.username!!, thumb)
saveAccountInteractor.save(account)
}
} }
\ 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.authentication.login.ui.LoginFragment import chat.rocket.android.authentication.login.ui.LoginFragment
...@@ -11,7 +10,7 @@ import chat.rocket.android.main.ui.MainActivity ...@@ -11,7 +10,7 @@ import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.util.extensions.addFragmentBackStack import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.webview.ui.webViewIntent import chat.rocket.android.webview.ui.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity, internal val context: Context) { class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
fun toLogin() { fun toLogin() {
activity.addFragmentBackStack("LoginFragment", R.id.fragment_container) { activity.addFragmentBackStack("LoginFragment", R.id.fragment_container) {
...@@ -32,7 +31,7 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity, int ...@@ -32,7 +31,7 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity, int
} }
fun toWebPage(url: String) { fun toWebPage(url: String) {
activity.startActivity(context.webViewIntent(url)) activity.startActivity(activity.webViewIntent(url))
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
......
...@@ -3,6 +3,8 @@ package chat.rocket.android.authentication.presentation ...@@ -3,6 +3,8 @@ package chat.rocket.android.authentication.presentation
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.MultiServerTokenRepository import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
import chat.rocket.core.TokenRepository import chat.rocket.core.TokenRepository
import javax.inject.Inject import javax.inject.Inject
...@@ -13,12 +15,12 @@ class AuthenticationPresenter @Inject constructor(private val navigator: Authent ...@@ -13,12 +15,12 @@ class AuthenticationPresenter @Inject constructor(private val navigator: Authent
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val tokenRepository: TokenRepository) { private val tokenRepository: TokenRepository) {
fun loadCredentials(callback: (authenticated: Boolean) -> Unit) { fun loadCredentials(newServer: Boolean, callback: (authenticated: Boolean) -> Unit) {
val currentServer = getCurrentServerInteractor.get() val currentServer = getCurrentServerInteractor.get()
val serverToken = currentServer?.let { multiServerRepository.get(currentServer) } val serverToken = currentServer?.let { multiServerRepository.get(currentServer) }
val settings = currentServer?.let { settingsRepository.get(currentServer) } val settings = currentServer?.let { settingsRepository.get(currentServer) }
if (currentServer == null || serverToken == null || settings == null) { if (newServer || currentServer == null || serverToken == null || settings == null) {
callback(false) callback(false)
} else { } else {
tokenRepository.save(Token(serverToken.userId, serverToken.authToken)) tokenRepository.save(Token(serverToken.userId, serverToken.authToken))
......
...@@ -56,13 +56,21 @@ class ServerFragment : Fragment(), ServerView { ...@@ -56,13 +56,21 @@ class ServerFragment : Fragment(), ServerView {
enableUserInput(true) enableUserInput(true)
} }
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int){
showToast(resId)
}
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() {
showMessage(getString(R.string.msg_generic_error))
}
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection)) override fun showNoInternetConnection() {
showMessage(getString(R.string.msg_no_internet_connection))
}
private fun enableUserInput(value: Boolean) { private fun enableUserInput(value: Boolean) {
button_connect.isEnabled = value button_connect.isEnabled = value
......
...@@ -5,7 +5,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -5,7 +5,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
...@@ -15,6 +17,7 @@ import chat.rocket.core.internal.rest.login ...@@ -15,6 +17,7 @@ import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.internal.rest.signup import chat.rocket.core.internal.rest.signup
import chat.rocket.core.model.Myself
import javax.inject.Inject import javax.inject.Inject
class SignupPresenter @Inject constructor(private val view: SignupView, class SignupPresenter @Inject constructor(private val view: SignupView,
...@@ -22,8 +25,12 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -22,8 +25,12 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val factory: RocketChatClientFactory) { private val factory: RocketChatClientFactory,
private val client: RocketChatClient = factory.create(serverInteractor.get()!!) private val saveAccountInteractor: SaveAccountInteractor,
settingsInteractor: GetSettingsInteractor) {
private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun signup(name: String, username: String, password: String, email: String) { fun signup(name: String, username: String, password: String, email: String) {
val server = serverInteractor.get() val server = serverInteractor.get()
...@@ -56,6 +63,7 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -56,6 +63,7 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
client.login(username, password) client.login(username, password)
val me = client.me() val me = client.me()
localRepository.save(LocalRepository.USERNAME_KEY, me.username) localRepository.save(LocalRepository.USERNAME_KEY, me.username)
saveAccount(me)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
...@@ -94,4 +102,16 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -94,4 +102,16 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
} }
// TODO: Schedule push token registering when it comes up null // TODO: Schedule push token registering when it comes up null
} }
private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val thumb = UrlHelper.getAvatarUrl(currentServer, me.username!!)
val account = Account(currentServer, icon, logo, me.username!!, thumb)
saveAccountInteractor.save(account)
}
} }
\ No newline at end of file
...@@ -97,9 +97,13 @@ class SignupFragment : Fragment(), SignupView { ...@@ -97,9 +97,13 @@ class SignupFragment : Fragment(), SignupView {
enableUserInput(true) enableUserInput(true)
} }
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() { override fun showGenericErrorMessage() {
showMessage(getString(R.string.msg_generic_error)) showMessage(getString(R.string.msg_generic_error))
......
...@@ -4,9 +4,10 @@ import chat.rocket.android.authentication.domain.model.TokenModel ...@@ -4,9 +4,10 @@ import chat.rocket.android.authentication.domain.model.TokenModel
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.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.MultiServerTokenRepository import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
...@@ -14,7 +15,9 @@ import chat.rocket.common.RocketChatException ...@@ -14,7 +15,9 @@ import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
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.me
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.model.Myself
import javax.inject.Inject import javax.inject.Inject
class TwoFAPresenter @Inject constructor(private val view: TwoFAView, class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
...@@ -23,8 +26,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -23,8 +26,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val multiServerRepository: MultiServerTokenRepository, private val multiServerRepository: MultiServerTokenRepository,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val factory: RocketChatClientFactory) { private val factory: RocketChatClientFactory,
private val client: RocketChatClient = factory.create(serverInteractor.get()!!) private val saveAccountInteractor: SaveAccountInteractor,
settingsInteractor: GetSettingsInteractor) {
private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
// TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText) // TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText)
fun authenticate(usernameOrEmail: String, password: String, twoFactorAuthenticationCode: String) { fun authenticate(usernameOrEmail: String, password: String, twoFactorAuthenticationCode: String) {
...@@ -45,6 +52,8 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -45,6 +52,8 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
// The token is saved via the client TokenProvider // The token is saved via the client TokenProvider
val token = val token =
client.login(usernameOrEmail, password, twoFactorAuthenticationCode) client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
val me = client.me()
saveAccount(me)
multiServerRepository.save( multiServerRepository.save(
server, server,
TokenModel(token.userId, token.authToken) TokenModel(token.userId, token.authToken)
...@@ -80,4 +89,16 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -80,4 +89,16 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
} }
// TODO: Schedule push token registering when it comes up null // TODO: Schedule push token registering when it comes up null
} }
private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val thumb = UrlHelper.getAvatarUrl(currentServer, me.username!!)
val account = Account(currentServer, icon, logo, me.username!!, thumb)
saveAccountInteractor.save(account)
}
} }
\ No newline at end of file
...@@ -66,7 +66,9 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -66,7 +66,9 @@ class TwoFAFragment : Fragment(), TwoFAView {
text_two_factor_auth.shake() text_two_factor_auth.shake()
} }
override fun alertInvalidTwoFactorAuthenticationCode() = showMessage(getString(R.string.msg_invalid_2fa_code)) override fun alertInvalidTwoFactorAuthenticationCode() {
showMessage(getString(R.string.msg_invalid_2fa_code))
}
override fun showLoading() { override fun showLoading() {
enableUserInput(false) enableUserInput(false)
...@@ -78,9 +80,13 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -78,9 +80,13 @@ class TwoFAFragment : Fragment(), TwoFAView {
enableUserInput(true) enableUserInput(true)
} }
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......
package chat.rocket.android.authentication.ui package chat.rocket.android.authentication.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle 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
...@@ -19,7 +21,9 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -19,7 +21,9 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this) AndroidInjection.inject(this)
presenter.loadCredentials { authenticated -> val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false)
presenter.loadCredentials(newServer) { authenticated ->
if (authenticated) { if (authenticated) {
// just call onCreate, and the presenter will call the navigator... // just call onCreate, and the presenter will call the navigator...
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -43,4 +47,13 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -43,4 +47,13 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
ServerFragment.newInstance() ServerFragment.newInstance()
} }
} }
}
const val INTENT_ADD_NEW_SERVER = "INTENT_ADD_NEW_SERVER"
fun Context.newServerIntent(): Intent {
return Intent(this, AuthenticationActivity::class.java).apply {
putExtra(INTENT_ADD_NEW_SERVER, true)
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
} }
\ No newline at end of file
...@@ -229,7 +229,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -229,7 +229,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.uploadFile(chatRoomId, uri, "") presenter.uploadFile(chatRoomId, uri, "")
} }
override fun showInvalidFileMessage() = showMessage(getString(R.string.msg_invalid_file)) override fun showInvalidFileMessage() {
showMessage(getString(R.string.msg_invalid_file))
}
override fun showNewMessage(message: List<BaseViewModel<*>>) { override fun showNewMessage(message: List<BaseViewModel<*>>) {
adapter.prependData(message) adapter.prependData(message)
...@@ -283,9 +285,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -283,9 +285,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun hideLoading() = view_loading.setVisible(false) override fun hideLoading() = view_loading.setVisible(false)
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......
...@@ -66,9 +66,13 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { ...@@ -66,9 +66,13 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
override fun hideLoading() = view_loading.setVisible(false) override fun hideLoading() = view_loading.setVisible(false)
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......
...@@ -135,7 +135,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -135,7 +135,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private fun updateRooms() { private fun updateRooms() {
Timber.d("Updating Rooms") Timber.d("Updating Rooms")
launch { launch(strategy.jobs) {
view.updateChatRooms(getChatRoomsInteractor.get(currentServer)) view.updateChatRooms(getChatRoomsInteractor.get(currentServer))
} }
} }
......
...@@ -109,9 +109,13 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -109,9 +109,13 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun hideLoading() = view_loading.setVisible(false) override fun hideLoading() = view_loading.setVisible(false)
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......
...@@ -16,6 +16,8 @@ import chat.rocket.android.main.di.MainModule ...@@ -16,6 +16,8 @@ import chat.rocket.android.main.di.MainModule
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.members.di.MembersFragmentProvider import chat.rocket.android.members.di.MembersFragmentProvider
import chat.rocket.android.profile.di.ProfileFragmentProvider import chat.rocket.android.profile.di.ProfileFragmentProvider
import chat.rocket.android.server.di.ChangeServerModule
import chat.rocket.android.server.ui.ChangeServerActivity
import chat.rocket.android.settings.password.di.PasswordFragmentProvider import chat.rocket.android.settings.password.di.PasswordFragmentProvider
import chat.rocket.android.settings.password.ui.PasswordActivity import chat.rocket.android.settings.password.ui.PasswordActivity
import dagger.Module import dagger.Module
...@@ -51,4 +53,8 @@ abstract class ActivityBuilder { ...@@ -51,4 +53,8 @@ abstract class ActivityBuilder {
@PerActivity @PerActivity
@ContributesAndroidInjector(modules = [PasswordFragmentProvider::class]) @ContributesAndroidInjector(modules = [PasswordFragmentProvider::class])
abstract fun bindPasswordActivity(): PasswordActivity abstract fun bindPasswordActivity(): PasswordActivity
@PerActivity
@ContributesAndroidInjector(modules = [ChangeServerModule::class])
abstract fun bindChangeServerActivity(): ChangeServerActivity
} }
\ No newline at end of file
...@@ -240,4 +240,9 @@ class AppModule { ...@@ -240,4 +240,9 @@ class AppModule {
fun providePermissionInteractor(settingsRepository: SettingsRepository, serverRepository: CurrentServerRepository): GetPermissionsInteractor { fun providePermissionInteractor(settingsRepository: SettingsRepository, serverRepository: CurrentServerRepository): GetPermissionsInteractor {
return GetPermissionsInteractor(settingsRepository, serverRepository) return GetPermissionsInteractor(settingsRepository, serverRepository)
} }
@Provides
@Singleton
fun provideAccountsRepository(preferences: SharedPreferences, moshi: Moshi): AccountsRepository =
SharedPreferencesAccountsRepository(preferences, moshi)
} }
\ No newline at end of file
...@@ -14,6 +14,16 @@ object UrlHelper { ...@@ -14,6 +14,16 @@ object UrlHelper {
fun getAvatarUrl(serverUrl: String, avatarName: String, format: String = "jpeg"): String = fun getAvatarUrl(serverUrl: String, avatarName: String, format: String = "jpeg"): String =
removeTrailingSlash(serverUrl) + "/avatar/" + removeTrailingSlash(avatarName) + "?format=$format" removeTrailingSlash(serverUrl) + "/avatar/" + removeTrailingSlash(avatarName) + "?format=$format"
/**
* Returns the server logo URL.
*
* @param serverUrl The server URL.
* @param favicon The faviconLarge from the server settings.
* @return The server logo URL.
*/
fun getServerLogoUrl(serverUrl: String, favicon: String): String =
removeTrailingSlash(serverUrl) + "/$favicon"
/** /**
* Returns the CAS URL. * Returns the CAS URL.
* *
......
package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView
import android.view.View
import chat.rocket.android.server.domain.model.Account
import kotlinx.android.synthetic.main.item_account.view.*
class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(account: Account) {
with(itemView) {
server_logo.setImageURI(account.serverLogo)
text_server_url.text = account.serverUrl
text_username.text = account.userName
}
}
}
\ No newline at end of file
package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.util.extensions.inflate
class AccountsAdapter(
private val accounts: List<Account>,
private val selector: AccountSelector
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_ACCOUNT -> AccountViewHolder(parent.inflate(R.layout.item_account))
else -> AddAccountViewHolder(parent.inflate(R.layout.item_add_account))
}
}
override fun getItemCount() = accounts.size + 1
override fun getItemViewType(position: Int) =
if (position == accounts.size) VIEW_TYPE_ADD_ACCOUNT else VIEW_TYPE_ACCOUNT
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is AccountViewHolder -> bindAccountViewHolder(holder, position)
is AddAccountViewHolder -> bindAddAccountViewHolder(holder, position)
}
}
private fun bindAccountViewHolder(holder: AccountViewHolder, position: Int) {
val account = accounts[position]
holder.bind(account)
holder.itemView.setOnClickListener {
selector.onAccountSelected(account.serverUrl)
}
}
private fun bindAddAccountViewHolder(holder: AddAccountViewHolder, position: Int) {
holder.itemView.setOnClickListener {
selector.onAddedAccountSelected()
}
}
}
interface AccountSelector {
fun onAccountSelected(serverUrl: String)
fun onAddedAccountSelected()
}
private const val VIEW_TYPE_ACCOUNT = 0
private const val VIEW_TYPE_ADD_ACCOUNT = 1
\ No newline at end of file
package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView
import android.view.View
class AddAccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
\ No newline at end of file
...@@ -16,7 +16,7 @@ class MainModule { ...@@ -16,7 +16,7 @@ class MainModule {
@Provides @Provides
@PerActivity @PerActivity
fun provideMainNavigator(activity: MainActivity, context: Context) = MainNavigator(activity, context) fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity)
@Provides @Provides
fun provideMainView(activity: MainActivity): MainView = activity fun provideMainView(activity: MainActivity): MainView = activity
......
...@@ -2,14 +2,16 @@ package chat.rocket.android.main.presentation ...@@ -2,14 +2,16 @@ package chat.rocket.android.main.presentation
import android.content.Context import android.content.Context
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.chatroom.ui.chatRoomIntent import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.profile.ui.ProfileFragment import chat.rocket.android.profile.ui.ProfileFragment
import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.settings.ui.SettingsFragment import chat.rocket.android.settings.ui.SettingsFragment
import chat.rocket.android.util.extensions.addFragment import chat.rocket.android.util.extensions.addFragment
class MainNavigator(internal val activity: MainActivity, internal val context: Context) { class MainNavigator(internal val activity: MainActivity) {
fun toChatList() { fun toChatList() {
activity.addFragment("ChatRoomsFragment", R.id.fragment_container) { activity.addFragment("ChatRoomsFragment", R.id.fragment_container) {
...@@ -35,8 +37,17 @@ class MainNavigator(internal val activity: MainActivity, internal val context: C ...@@ -35,8 +37,17 @@ class MainNavigator(internal val activity: MainActivity, internal val context: C
isChatRoomReadOnly: Boolean, isChatRoomReadOnly: Boolean,
chatRoomLastSeen: Long, chatRoomLastSeen: Long,
isChatRoomSubscribed: Boolean) { isChatRoomSubscribed: Boolean) {
activity.startActivity(context.chatRoomIntent(chatRoomId, chatRoomName, chatRoomType, activity.startActivity(activity.chatRoomIntent(chatRoomId, chatRoomName, chatRoomType,
isChatRoomReadOnly, chatRoomLastSeen, isChatRoomSubscribed)) isChatRoomReadOnly, chatRoomLastSeen, isChatRoomSubscribed))
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit) activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
} }
fun toNewServer(serverUrl: String? = null) {
activity.startActivity(activity.changeServerIntent(serverUrl))
activity.finish()
}
fun toServerScreen() {
activity.startActivity(activity.newServerIntent())
}
} }
\ No newline at end of file
package chat.rocket.android.main.presentation package chat.rocket.android.main.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.main.viewmodel.NavHeaderViewModelMapper
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.realtime.disconnect
import chat.rocket.core.internal.rest.logout import chat.rocket.core.internal.rest.logout
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.unregisterPushToken import chat.rocket.core.internal.rest.unregisterPushToken
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MainPresenter @Inject constructor(private val view: MainView, class MainPresenter @Inject constructor(private val view: MainView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: MainNavigator, private val navigator: MainNavigator,
private val multiServerRepository: MultiServerTokenRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val navHeaderMapper: NavHeaderViewModelMapper,
private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
private val removeAccountInterector: RemoveAccountInterector,
getSettingsInteractor: GetSettingsInteractor,
managerFactory: ConnectionManagerFactory, managerFactory: ConnectionManagerFactory,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val manager = managerFactory.create(currentServer) private val manager = managerFactory.create(currentServer)
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!)
fun toChatList() = navigator.toChatList() fun toChatList() = navigator.toChatList()
...@@ -31,6 +43,32 @@ class MainPresenter @Inject constructor(private val view: MainView, ...@@ -31,6 +43,32 @@ class MainPresenter @Inject constructor(private val view: MainView,
fun toSettings() = navigator.toSettings() fun toSettings() = navigator.toSettings()
fun loadCurrentInfo() {
launchUI(strategy) {
try {
val me = client.me()
val model = navHeaderMapper.mapToViewModel(me)
saveAccount(model)
view.setupNavHeader(model, getAccountsInteractor.get())
} catch (ex: Exception) {
Timber.d(ex, "Error loading my information for navheader")
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
private suspend fun saveAccount(me: NavHeaderViewModel) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val account = Account(currentServer, icon, me.serverLogo, me.username, me.avatar)
saveAccountInteractor.save(account)
}
/** /**
* Logout from current server. * Logout from current server.
*/ */
...@@ -39,9 +77,10 @@ class MainPresenter @Inject constructor(private val view: MainView, ...@@ -39,9 +77,10 @@ class MainPresenter @Inject constructor(private val view: MainView,
try { try {
clearTokens() clearTokens()
client.logout() client.logout()
//TODO: Add the code to unsubscribe to all subscriptions. disconnect()
client.disconnect() removeAccountInterector.remove(currentServer)
view.onLogout() multiServerRepository.clear(currentServer)
navigator.toNewServer()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
...@@ -69,4 +108,16 @@ class MainPresenter @Inject constructor(private val view: MainView, ...@@ -69,4 +108,16 @@ class MainPresenter @Inject constructor(private val view: MainView,
fun disconnect() { fun disconnect() {
manager.disconnect() manager.disconnect()
} }
fun changeServer(serverUrl: String) {
if (currentServer != serverUrl) {
navigator.toNewServer(serverUrl)
} else {
view.closeServerSelection()
}
}
fun addNewServer() {
navigator.toServerScreen()
}
} }
\ No newline at end of file
package chat.rocket.android.main.presentation package chat.rocket.android.main.presentation
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.model.Account
interface MainView : MessageView { interface MainView : MessageView {
fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>)
/** fun closeServerSelection()
* User has successfully logged out from the current server.
**/
fun onLogout()
} }
\ No newline at end of file
...@@ -5,12 +5,21 @@ import android.content.Intent ...@@ -5,12 +5,21 @@ import android.content.Intent
import android.os.Bundle 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 android.support.v7.widget.LinearLayoutManager
import android.view.Gravity import android.view.Gravity
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.main.adapter.AccountSelector
import chat.rocket.android.main.adapter.AccountsAdapter
import chat.rocket.android.main.presentation.MainPresenter import chat.rocket.android.main.presentation.MainPresenter
import chat.rocket.android.main.presentation.MainView import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
...@@ -19,6 +28,8 @@ import dagger.android.HasActivityInjector ...@@ -19,6 +28,8 @@ import dagger.android.HasActivityInjector
import dagger.android.support.HasSupportFragmentInjector import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.nav_header.view.*
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupportFragmentInjector { class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupportFragmentInjector {
...@@ -33,6 +44,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -33,6 +44,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
presenter.connect() presenter.connect()
presenter.loadCurrentInfo()
setupToolbar() setupToolbar()
setupNavigationView() setupNavigationView()
} }
...@@ -52,11 +64,45 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -52,11 +64,45 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
} }
} }
override fun onLogout() { override fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>) {
finish() Timber.d("Setting up nav header: $model")
val intent = Intent(this, AuthenticationActivity::class.java) val headerLayout = view_navigation.getHeaderView(0)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) headerLayout.text_name.text = model.username
startActivity(intent) headerLayout.text_server.text = model.server
headerLayout.image_avatar.setImageURI(model.avatar)
headerLayout.server_logo.setImageURI(model.serverLogo)
setupAccountsList(headerLayout, accounts)
}
override fun closeServerSelection() {
}
private var expanded = false
private fun setupAccountsList(header: View, accounts: List<Account>) {
accounts_list.layoutManager = LinearLayoutManager(this)
accounts_list.adapter = AccountsAdapter(accounts, object : AccountSelector {
override fun onAccountSelected(serverUrl: String) {
presenter.changeServer(serverUrl)
}
override fun onAddedAccountSelected() {
presenter.addNewServer()
}
})
header.account_container.setOnClickListener {
header.account_expand.rotateBy(180f)
if (expanded) {
accounts_list.fadeOut()
} else {
accounts_list.fadeIn()
}
expanded = !expanded
}
} }
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) = showToast(resId)
......
package chat.rocket.android.main.viewmodel
data class NavHeaderViewModel(
val username: String,
val server: String,
val avatar: String?,
val serverLogo: String?
)
\ No newline at end of file
package chat.rocket.android.main.viewmodel
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.*
import chat.rocket.core.model.Myself
import javax.inject.Inject
class NavHeaderViewModelMapper @Inject constructor(serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor) {
private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!)
private val baseUrl = settings.baseUrl()!!
fun mapToViewModel(me: Myself): NavHeaderViewModel {
val username = mapUsername(me)
val thumb = me.username?.let { UrlHelper.getAvatarUrl(baseUrl, it) }
val image = settings.wideTile() ?: settings.faviconLarge()
val logo = image?.let { UrlHelper.getServerLogoUrl(baseUrl, it) }
return NavHeaderViewModel(username, baseUrl, thumb, logo)
}
private fun mapUsername(me: Myself): String {
val username = me.username
val realName = me.name
val senderName = if (settings.useRealName()) realName else username
return senderName ?: username.toString()
}
}
\ No newline at end of file
...@@ -96,9 +96,13 @@ class MembersFragment : Fragment(), MembersView { ...@@ -96,9 +96,13 @@ class MembersFragment : Fragment(), MembersView {
override fun hideLoading() = view_loading.setVisible(false) override fun hideLoading() = view_loading.setVisible(false)
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......
...@@ -66,7 +66,9 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -66,7 +66,9 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
listenToChanges() listenToChanges()
} }
override fun showProfileUpdateSuccessfullyMessage() = showMessage(getString(R.string.msg_profile_update_successfully)) override fun showProfileUpdateSuccessfullyMessage() {
showMessage(getString(R.string.msg_profile_update_successfully))
}
override fun showLoading() { override fun showLoading() {
enableUserInput(false) enableUserInput(false)
...@@ -78,9 +80,13 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -78,9 +80,13 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
enableUserInput(true) enableUserInput(true)
} }
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showMessage(message: String) = showToast(message) override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......
package chat.rocket.android.server.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.server.presentation.ChangeServerNavigator
import chat.rocket.android.server.presentation.ChangeServerView
import chat.rocket.android.server.ui.ChangeServerActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class ChangeServerModule {
@Provides
@PerActivity
fun provideChangeServerNavigator(activity: ChangeServerActivity) = ChangeServerNavigator(activity)
@Provides
@PerActivity
fun ChangeServerView(activity: ChangeServerActivity): ChangeServerView {
return activity
}
@Provides
fun provideLifecycleOwner(activity: ChangeServerActivity): LifecycleOwner = activity
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy = CancelStrategy(owner, jobs)
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.android.server.domain.model.Account
interface AccountsRepository {
suspend fun save(account: Account)
suspend fun load(): List<Account>
suspend fun remove(serverUrl: String)
}
\ No newline at end of file
package chat.rocket.android.server.domain
import javax.inject.Inject
class GetAccountsInteractor @Inject constructor(val repository: AccountsRepository) {
suspend fun get() = repository.load()
}
\ No newline at end of file
...@@ -17,11 +17,12 @@ class RefreshSettingsInteractor @Inject constructor(private val factory: RocketC ...@@ -17,11 +17,12 @@ class RefreshSettingsInteractor @Inject constructor(private val factory: RocketC
ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR, ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR,
ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, ACCOUNT_GITLAB, ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, ACCOUNT_GITLAB,
SITE_URL, SITE_NAME, FAVICON_512, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS, SITE_URL, SITE_NAME, FAVICON_512, FAVICON_196, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS,
FAVORITE_ROOMS, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE, UPLOAD_WHITELIST_MIMETYPES, FAVORITE_ROOMS, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE, UPLOAD_WHITELIST_MIMETYPES,
HIDE_USER_JOIN, HIDE_USER_LEAVE, HIDE_USER_JOIN, HIDE_USER_LEAVE,
HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING, HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS) ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS,
WIDE_TILE_310)
suspend fun refresh(server: String) { suspend fun refresh(server: String) {
withContext(CommonPool) { withContext(CommonPool) {
......
package chat.rocket.android.server.domain
import javax.inject.Inject
class RemoveAccountInterector @Inject constructor(val repository: AccountsRepository) {
suspend fun remove(serverUrl: String) {
repository.remove(serverUrl)
}
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.android.server.domain.model.Account
import javax.inject.Inject
class SaveAccountInteractor @Inject constructor(val repository: AccountsRepository) {
suspend fun save(account: Account) = repository.save(account)
}
\ No newline at end of file
package chat.rocket.android.server.domain;
public class SaveServerInteractor {
}
...@@ -29,7 +29,9 @@ const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab" ...@@ -29,7 +29,9 @@ const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab"
const val SITE_URL = "Site_Url" const val SITE_URL = "Site_Url"
const val SITE_NAME = "Site_Name" const val SITE_NAME = "Site_Name"
const val FAVICON_196 = "Assets_favicon_192"
const val FAVICON_512 = "Assets_favicon_512" const val FAVICON_512 = "Assets_favicon_512"
const val WIDE_TILE_310 = "Assets_tile_310_wide"
const val USE_REALNAME = "UI_Use_Real_Name" const val USE_REALNAME = "UI_Use_Real_Name"
const val ALLOW_ROOM_NAME_SPECIAL_CHARS = "UI_Allow_room_names_with_special_chars" const val ALLOW_ROOM_NAME_SPECIAL_CHARS = "UI_Allow_room_names_with_special_chars"
const val FAVORITE_ROOMS = "Favorite_Rooms" const val FAVORITE_ROOMS = "Favorite_Rooms"
...@@ -69,6 +71,9 @@ fun PublicSettings.isGitlabAuthenticationEnabled(): Boolean = this[ACCOUNT_GITLA ...@@ -69,6 +71,9 @@ fun PublicSettings.isGitlabAuthenticationEnabled(): Boolean = this[ACCOUNT_GITLA
fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true
fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true
fun PublicSettings.faviconLarge(): String? = this[FAVICON_512]?.value as String?
fun PublicSettings.favicon(): String? = this[FAVICON_196]?.value as String?
fun PublicSettings.wideTile(): String? = this[WIDE_TILE_310]?.value as String?
// Message settings // Message settings
fun PublicSettings.showDeletedStatus(): Boolean = this[SHOW_DELETED_STATUS]?.value == true fun PublicSettings.showDeletedStatus(): Boolean = this[SHOW_DELETED_STATUS]?.value == true
......
package chat.rocket.android.server.domain.model
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class Account(
val serverUrl: String,
val serverLogo: String?,
val serverBg: String?,
val userName: String,
val avatar: String?
)
\ No newline at end of file
...@@ -19,4 +19,6 @@ class ConnectionManagerFactory @Inject constructor(private val factory: RocketCh ...@@ -19,4 +19,6 @@ class ConnectionManagerFactory @Inject constructor(private val factory: RocketCh
cache[url] = manager cache[url] = manager
return manager return manager
} }
fun get(url: String) = cache[url]
} }
\ No newline at end of file
package chat.rocket.android.server.infraestructure
import android.content.SharedPreferences
import androidx.content.edit
import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.model.Account
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
class SharedPreferencesAccountsRepository(
private val preferences: SharedPreferences,
private val moshi: Moshi
) : AccountsRepository {
override suspend fun save(newAccount: Account) {
withContext(CommonPool) {
val accounts = load()
val newList = accounts.filter { account -> newAccount.serverUrl != account.serverUrl }
.toMutableList()
newList.add(0, newAccount)
save(newList)
}
}
override suspend fun load(): List<Account> = withContext(CommonPool) {
val json = preferences.getString(ACCOUNTS_KEY, "[]")
val type = Types.newParameterizedType(List::class.java, Account::class.java)
val adapter = moshi.adapter<List<Account>>(type)
adapter.fromJson(json) ?: emptyList()
}
override suspend fun remove(serverUrl: String) {
withContext(CommonPool) {
val accounts = load()
val newList = accounts.filter { account -> serverUrl != account.serverUrl }
.toMutableList()
save(newList)
}
}
private fun save(accounts: List<Account>) {
val type = Types.newParameterizedType(List::class.java, Account::class.java)
val adapter = moshi.adapter<List<Account>>(type)
preferences.edit {
putString(ACCOUNTS_KEY, adapter.toJson(accounts))
}
}
}
private const val ACCOUNTS_KEY = "ACCOUNTS_KEY"
\ No newline at end of file
package chat.rocket.android.server.presentation
import android.content.Intent
import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.ui.ChangeServerActivity
class ChangeServerNavigator (internal val activity: ChangeServerActivity) {
fun toServerScreen() {
activity.startActivity(activity.newServerIntent())
activity.finish()
}
fun toChatRooms() {
activity.startActivity(Intent(activity, MainActivity::class.java))
activity.finish()
}
}
\ No newline at end of file
package chat.rocket.android.server.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.model.Token
import chat.rocket.common.util.ifNull
import chat.rocket.core.TokenRepository
import javax.inject.Inject
class ChangeServerPresenter @Inject constructor(private val view: ChangeServerView,
private val navigator: ChangeServerNavigator,
private val strategy: CancelStrategy,
private val saveCurrentServerInteractor: SaveCurrentServerInteractor,
private val getCurrentServerInteractor: GetCurrentServerInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
private val multiServerRepository: MultiServerTokenRepository,
private val settingsRepository: SettingsRepository,
private val tokenRepository: TokenRepository,
private val connectionManager: ConnectionManagerFactory) {
fun loadServer(newUrl: String?) {
launchUI(strategy) {
view.showProgress()
var url = newUrl
if (url == null) { // Try to load next server on the list...
val accounts = getAccountsInteractor.get()
url = accounts.firstOrNull()?.serverUrl
}
url?.let { serverUrl ->
val token = multiServerRepository.get(serverUrl)
if (token == null) {
view.showInvalidCredentials()
view.hideProgress()
navigator.toServerScreen()
return@launchUI
}
val settings = settingsRepository.get(serverUrl)
if (settings == null) {
// TODO - reload settings...
}
// Call disconnect on the old url if any...
getCurrentServerInteractor.get()?.let { url ->
connectionManager.get(url)?.disconnect()
}
tokenRepository.save(Token(token.userId, token.authToken))
saveCurrentServerInteractor.save(serverUrl)
view.hideProgress()
navigator.toChatRooms()
}.ifNull {
view.hideProgress()
navigator.toServerScreen()
}
}
}
}
\ No newline at end of file
package chat.rocket.android.server.presentation
interface ChangeServerView {
fun showInvalidCredentials()
fun showProgress()
fun hideProgress()
}
\ No newline at end of file
package chat.rocket.android.server.ui
import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.server.presentation.ChangeServerPresenter
import chat.rocket.android.server.presentation.ChangeServerView
import chat.rocket.android.util.extensions.showToast
import dagger.android.AndroidInjection
import javax.inject.Inject
class ChangeServerActivity : AppCompatActivity(), ChangeServerView {
@Inject lateinit var presenter: ChangeServerPresenter
var progress: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
val serverUrl: String? = intent.getStringExtra(INTENT_SERVER_URL)
presenter.loadServer(serverUrl)
}
override fun showInvalidCredentials() {
showToast("Missing credentials for this server")
}
override fun showProgress() {
progress = ProgressDialog.show(this, "Rocket.Chat", "Changing Server")
}
override fun hideProgress() {
progress?.dismiss()
}
}
private const val INTENT_SERVER_URL = "INTENT_SERVER_URL"
private const val INTENT_CHAT_ROOM_NAME = "INTENT_CHAT_ROOM_NAME"
private const val INTENT_CHAT_ROOM_TYPE = "INTENT_CHAT_ROOM_NAME"
fun Context.changeServerIntent(serverUrl: String?): Intent {
return Intent(this, ChangeServerActivity::class.java).apply {
serverUrl?.let { url ->
putExtra(INTENT_SERVER_URL, url)
}
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
}
\ No newline at end of file
...@@ -29,12 +29,12 @@ fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long ...@@ -29,12 +29,12 @@ fun View.fadeIn(startValue: Float = 0f, finishValue: Float = 1f, duration: Long
animate() animate()
.alpha(startValue) .alpha(startValue)
.setDuration(duration) .setDuration(duration / 2)
.setInterpolator(DecelerateInterpolator()) .setInterpolator(DecelerateInterpolator())
.withEndAction({ .withEndAction({
animate() animate()
.alpha(finishValue) .alpha(finishValue)
.setDuration(duration) .setDuration(duration / 2)
.setInterpolator(AccelerateInterpolator()).start() .setInterpolator(AccelerateInterpolator()).start()
}).start() }).start()
......
...@@ -27,7 +27,8 @@ fun View.isVisible(): Boolean { ...@@ -27,7 +27,8 @@ fun View.isVisible(): Boolean {
return visibility == View.VISIBLE return visibility == View.VISIBLE
} }
fun ViewGroup.inflate(@LayoutRes resource: Int): View = LayoutInflater.from(context).inflate(resource, this, false) fun ViewGroup.inflate(@LayoutRes resource: Int, attachToRoot: Boolean = false): View =
LayoutInflater.from(context).inflate(resource, this, attachToRoot)
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()
...@@ -36,10 +37,12 @@ fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> ...@@ -36,10 +37,12 @@ fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () ->
.commit() .commit()
} }
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() supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right) .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) .replace(layoutId, fragment, tag)
.addToBackStack(tag) .addToBackStack(tag)
.commit() .commit()
...@@ -50,13 +53,17 @@ fun Activity.hideKeyboard() { ...@@ -50,13 +53,17 @@ fun Activity.hideKeyboard() {
imm.hideSoftInputFromWindow(currentFocus.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN) imm.hideSoftInputFromWindow(currentFocus.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
} }
fun Activity.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) = showToast(getString(resource), duration) fun Activity.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) =
showToast(getString(resource), duration)
fun Activity.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, message, duration).show() fun Activity.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
Toast.makeText(this, message, duration).show()
fun Fragment.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) = showToast(getString(resource), duration) fun Fragment.showToast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) =
showToast(getString(resource), duration)
fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = activity!!.showToast(message, duration) fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
activity?.showToast(message, duration)
fun RecyclerView.isAtBottom(): Boolean { fun RecyclerView.isAtBottom(): Boolean {
val manager: RecyclerView.LayoutManager? = layoutManager val manager: RecyclerView.LayoutManager? = layoutManager
......
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="#00000000"
android:centerColor="#30000000"
android:startColor="#C0000000"
android:type="linear" />
</shape>
\ No newline at end of file
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF2F343D" android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
</vector>
\ 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="#FF000000"
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
</vector>
...@@ -23,11 +23,26 @@ ...@@ -23,11 +23,26 @@
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</LinearLayout> </LinearLayout>
<android.support.design.widget.NavigationView <FrameLayout
android:id="@+id/view_navigation" android:id="@+id/navigation_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start">
app:menu="@menu/navigation" /> <android.support.design.widget.NavigationView
android:id="@+id/view_navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:menu="@menu/navigation"
app:headerLayout="@layout/nav_header" />
<android.support.v7.widget.RecyclerView
android:id="@+id/accounts_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/nav_header_height"
android:elevation="20dp"
android:background="@color/white"
android:alpha="0"
android:visibility="gone"/>
</FrameLayout>
</android.support.v4.widget.DrawerLayout> </android.support.v4.widget.DrawerLayout>
\ No newline at end of file
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground" >
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/server_logo"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:actualImageScaleType="centerInside" />
<TextView
android:id="@+id/text_server_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="@id/server_logo"
app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintEnd_toEndOf="parent"
tools:text="https://open.rocket.chat" />
<TextView
android:id="@+id/text_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@id/server_logo"
app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintEnd_toEndOf="parent"
tools:text="Lucio Maciel"/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground" >
<ImageView
android:id="@+id/server_logo"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_add_24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:text="@string/add_account"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintEnd_toEndOf="parent" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?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:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/server_logo"
android:layout_width="0dp"
android:layout_height="0dp"
android:foreground="@drawable/black_gradient"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:actualImageScaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_avatar"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundedCornerRadius="3dp"
tools:src="@tools:sample/avatars" />
<android.support.constraint.ConstraintLayout
android:id="@+id/account_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginTop="16dp"
android:padding="4dp"
android:elevation="2dp"
android:background="?selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/image_avatar"
app:layout_constraintTop_toBottomOf="@+id/image_avatar">
<TextView
android:id="@+id/text_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@+id/account_expand"
app:layout_constraintStart_toStartOf="parent"
tools:text="Lucio Maciel" />
<TextView
android:id="@+id/text_server"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@+id/account_expand"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_name"
tools:text="https://open.rocket.chat" />
<ImageView
android:id="@+id/account_expand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_expand_more_24dp"
android:tint="@color/whitesmoke"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
<color name="actionMenuColor">#FF727272</color> <color name="actionMenuColor">#FF727272</color>
<color name="whitesmoke">#FFf1f1f1</color> <color name="whitesmoke">#FFf1f1f1</color>
<color name="translucent_white">#70F1F1F1</color>
<color name="colorEmojiIcon">#FF767676</color> <color name="colorEmojiIcon">#FF767676</color>
<!-- Suggestions --> <!-- Suggestions -->
......
...@@ -35,5 +35,6 @@ ...@@ -35,5 +35,6 @@
<!-- Autocomplete Popup --> <!-- Autocomplete Popup -->
<dimen name="popup_max_height">150dp</dimen> <dimen name="popup_max_height">150dp</dimen>
<dimen name="suggestions_box_max_height">250dp</dimen> <dimen name="suggestions_box_max_height">250dp</dimen>
<dimen name="nav_header_height">160dp</dimen>
</resources> </resources>
\ No newline at end of file
...@@ -138,5 +138,6 @@ ...@@ -138,5 +138,6 @@
<string name="Join_the_given_channel">Join the given channel</string> <string name="Join_the_given_channel">Join the given channel</string>
<string name="Guggy_Command_Description">Generates a gif based upon the provided text</string> <string name="Guggy_Command_Description">Generates a gif based upon the provided text</string>
<string name="Slash_Topic_Description">Set topic</string> <string name="Slash_Topic_Description">Set topic</string>
<string name="add_account">Add Account</string>
</resources> </resources>
\ No newline at end of file
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