Unverified Commit 35fe51af authored by divyanshu bhargava's avatar divyanshu bhargava Committed by GitHub

Merge branch 'develop-2.x' into RTL-layout

parents ed1635be c9fe5d91
...@@ -13,8 +13,8 @@ android { ...@@ -13,8 +13,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2003 versionCode 2006
versionName "2.0.0-beta3" versionName "2.0.0-beta4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
} }
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"> android:supportsRtl="true">
<activity <activity
android:name=".authentication.ui.AuthenticationActivity" android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation" android:configChanges="orientation"
...@@ -34,41 +33,33 @@ ...@@ -34,41 +33,33 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".server.ui.ChangeServerActivity" android:name=".server.ui.ChangeServerActivity"
android:theme="@style/AuthenticationTheme" /> android:theme="@style/AuthenticationTheme" />
<activity <activity
android:name=".main.ui.MainActivity" android:name=".main.ui.MainActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:theme="@style/AppTheme"
android:theme="@style/AppTheme" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity <activity
android:name=".webview.ui.WebViewActivity" android:name=".webview.ui.WebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:theme="@style/AppTheme"
android:theme="@style/AppTheme" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity <activity
android:name=".webview.cas.ui.CasWebViewActivity" android:name=".webview.cas.ui.CasWebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:theme="@style/AppTheme"
android:theme="@style/AppTheme" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity <activity
android:name=".webview.oauth.ui.OauthWebViewActivity" android:name=".webview.oauth.ui.OauthWebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:theme="@style/AppTheme"
android:theme="@style/AppTheme" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity <activity
android:name=".chatroom.ui.ChatRoomActivity" android:name=".chatroom.ui.ChatRoomActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:theme="@style/AppTheme"
android:theme="@style/AppTheme" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity <activity
android:name=".chatroom.ui.PinnedMessagesActivity" android:name=".chatroom.ui.PinnedMessagesActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:theme="@style/AppTheme"
android:theme="@style/AppTheme" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity <activity
android:name=".settings.password.ui.PasswordActivity" android:name=".settings.password.ui.PasswordActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
...@@ -92,7 +83,6 @@ ...@@ -92,7 +83,6 @@
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" /> <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter> </intent-filter>
</service> </service>
<service <service
android:name=".push.GcmListenerService" android:name=".push.GcmListenerService"
android:exported="false"> android:exported="false">
...@@ -104,6 +94,9 @@ ...@@ -104,6 +94,9 @@
<meta-data <meta-data
android:name="io.fabric.ApiKey" android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" /> android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
<activity android:name=".settings.about.ui.AboutActivity"
android:theme="@style/AppTheme"/>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -106,15 +106,45 @@ object DrawableHelper { ...@@ -106,15 +106,45 @@ object DrawableHelper {
* @return The user status drawable. * @return The user status drawable.
*/ */
fun getUserStatusDrawable(userStatus: UserStatus, context: Context): Drawable { fun getUserStatusDrawable(userStatus: UserStatus, context: Context): Drawable {
val userStatusDrawable = getDrawableFromId(R.drawable.ic_user_status_black, context).mutate() return when (userStatus) {
wrapDrawable(userStatusDrawable) is UserStatus.Online -> {
when (userStatus) { getDrawableFromId(R.drawable.ic_status_online_24dp, context)
is UserStatus.Online -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusOnline) }
is UserStatus.Busy -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusBusy) is UserStatus.Away -> {
is UserStatus.Away -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusAway) getDrawableFromId(R.drawable.ic_status_away_24dp, context)
is UserStatus.Offline -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusOffline) }
else -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusOffline) is UserStatus.Busy -> {
getDrawableFromId(R.drawable.ic_status_busy_24dp, context)
}
else -> getDrawableFromId(R.drawable.ic_status_invisible_24dp, context)
}
}
// TODO Why we need two UserStatus?
/**
* Returns the user status drawable.
*
* @param userStatus The user status.
* @param context The context.
* @sse [chat.rocket.core.internal.realtime.UserStatus]
* @return The user status drawable.
*/
fun getUserStatusDrawable(
userStatus: chat.rocket.core.internal.realtime.UserStatus,
context: Context
): Drawable {
return when (userStatus) {
is chat.rocket.core.internal.realtime.UserStatus.Online -> {
getDrawableFromId(R.drawable.ic_status_online_24dp, context)
}
is chat.rocket.core.internal.realtime.UserStatus.Away -> {
getDrawableFromId(R.drawable.ic_status_away_24dp, context)
}
is chat.rocket.core.internal.realtime.UserStatus.Busy -> {
getDrawableFromId(R.drawable.ic_status_busy_24dp, context)
}
else -> getDrawableFromId(R.drawable.ic_status_invisible_24dp, context)
} }
return userStatusDrawable
} }
} }
\ No newline at end of file
...@@ -18,10 +18,11 @@ import chat.rocket.android.app.migration.model.RealmUser ...@@ -18,10 +18,11 @@ import chat.rocket.android.app.migration.model.RealmUser
import chat.rocket.android.authentication.domain.model.toToken import chat.rocket.android.authentication.domain.model.toToken
import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.helper.CrashlyticsTree import chat.rocket.android.helper.CrashlyticsTree
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
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.domain.model.Account
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.widget.emoji.EmojiRepository import chat.rocket.android.widget.emoji.EmojiRepository
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
import chat.rocket.core.model.Value import chat.rocket.core.model.Value
...@@ -148,12 +149,12 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -148,12 +149,12 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
private fun migrateServerInfo(url: String, authToken: String, settings: PublicSettings, user: RealmUser) { private fun migrateServerInfo(url: String, authToken: String, settings: PublicSettings, user: RealmUser) {
val userId = user._id val userId = user._id
val avatar = UrlHelper.getAvatarUrl(url, user.username!!) val avatar = url.avatarUrl(user.username!!)
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(url, it) url.serverLogoUrl(it)
} }
val logo = settings.wideTile()?.let { val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(url, it) url.serverLogoUrl(it)
} }
val account = Account(url, icon, logo, user.username!!, avatar) val account = Account(url, icon, logo, user.username!!, avatar)
launch(CommonPool) { launch(CommonPool) {
......
package chat.rocket.android.authentication.login.presentation package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.BuildConfig
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.OauthHelper import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
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.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
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 timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
...@@ -103,7 +100,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -103,7 +100,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private fun setupCasView() { private fun setupCasView() {
if (settings.isCasAuthenticationEnabled()) { if (settings.isCasAuthenticationEnabled()) {
val token = generateRandomString(17) val token = generateRandomString(17)
view.setupCasButtonListener(UrlHelper.getCasUrl(settings.casLoginUrl(), currentServer, token), token) view.setupCasButtonListener(settings.casLoginUrl().casUrl(currentServer, token), token)
view.showCasButton() view.showCasButton()
} }
} }
...@@ -118,7 +115,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -118,7 +115,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private fun setupOauthServicesView() { private fun setupOauthServicesView() {
launchUI(strategy) { launchUI(strategy) {
try { try {
val services = client.settingsOauth().services val services = retryIO("settingsOauth()") {
client.settingsOauth().services
}
if (services.isNotEmpty()) { if (services.isNotEmpty()) {
val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64() val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
var totalSocialAccountsEnabled = 0 var totalSocialAccountsEnabled = 0
...@@ -195,30 +194,32 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -195,30 +194,32 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
view.disableUserInput() view.disableUserInput()
view.showLoading() view.showLoading()
try { try {
val token = when (loginType) { val token = retryIO("login") {
TYPE_LOGIN_USER_EMAIL -> { when (loginType) {
if (usernameOrEmail.isEmail()) { TYPE_LOGIN_USER_EMAIL -> {
client.loginWithEmail(usernameOrEmail, password) if (usernameOrEmail.isEmail()) {
} else { client.loginWithEmail(usernameOrEmail, password)
if (settings.isLdapAuthenticationEnabled()) {
client.loginWithLdap(usernameOrEmail, password)
} else { } else {
client.login(usernameOrEmail, password) if (settings.isLdapAuthenticationEnabled()) {
client.loginWithLdap(usernameOrEmail, password)
} else {
client.login(usernameOrEmail, password)
}
} }
} }
} TYPE_LOGIN_CAS -> {
TYPE_LOGIN_CAS -> { delay(3, TimeUnit.SECONDS)
delay(3, TimeUnit.SECONDS) client.loginWithCas(credentialToken)
client.loginWithCas(credentialToken) }
} TYPE_LOGIN_OAUTH -> {
TYPE_LOGIN_OAUTH -> { client.loginWithOauth(credentialToken, credentialSecret)
client.loginWithOauth(credentialToken, credentialSecret) }
} else -> {
else -> { throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS or TYPE_LOGIN_OAUTH")
throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS or TYPE_LOGIN_OAUTH") }
} }
} }
val username = client.me().username val username = retryIO("me()") { client.me().username }
if (username != null) { if (username != null) {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
saveAccount(username) saveAccount(username)
...@@ -258,12 +259,12 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -258,12 +259,12 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private suspend fun saveAccount(username: String) { private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val logo = settings.wideTile()?.let { val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val thumb = UrlHelper.getAvatarUrl(currentServer, username) val thumb = currentServer.avatarUrl(username)
val account = Account(currentServer, icon, logo, username, thumb) val account = Account(currentServer, icon, logo, username, thumb)
saveAccountInteractor.save(account) saveAccountInteractor.save(account)
} }
......
...@@ -3,13 +3,15 @@ package chat.rocket.android.authentication.registerusername.presentation ...@@ -3,13 +3,15 @@ package chat.rocket.android.authentication.registerusername.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.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.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account 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.avatarUrl
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.registerPushToken import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -41,7 +43,9 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -41,7 +43,9 @@ class RegisterUsernamePresenter @Inject constructor(
if (NetworkHelper.hasInternetAccess()) { if (NetworkHelper.hasInternetAccess()) {
view.showLoading() view.showLoading()
try { try {
val me = client.updateOwnBasicInformation(username = username) val me = retryIO("updateOwnBasicInformation(username = $username)") {
client.updateOwnBasicInformation(username = username)
}
val registeredUsername = me.username val registeredUsername = me.username
if (registeredUsername != null) { if (registeredUsername != null) {
saveAccount(registeredUsername) saveAccount(registeredUsername)
...@@ -75,12 +79,12 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -75,12 +79,12 @@ class RegisterUsernamePresenter @Inject constructor(
private suspend fun saveAccount(username: String) { private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val logo = settings.wideTile()?.let { val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val thumb = UrlHelper.getAvatarUrl(currentServer, username) val thumb = currentServer.avatarUrl(username)
val account = Account(currentServer, icon, logo, username, thumb) val account = Account(currentServer, icon, logo, username, thumb)
saveAccountInteractor.save(account) saveAccountInteractor.save(account)
} }
......
...@@ -3,10 +3,10 @@ package chat.rocket.android.authentication.server.presentation ...@@ -3,10 +3,10 @@ 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.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.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.util.extensions.isValidUrl
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import javax.inject.Inject import javax.inject.Inject
...@@ -18,7 +18,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView, ...@@ -18,7 +18,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView,
private val refreshSettingsInteractor: RefreshSettingsInteractor, private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor) { private val getAccountsInteractor: GetAccountsInteractor) {
fun connect(server: String) { fun connect(server: String) {
if (!UrlHelper.isValidUrl(server)) { if (!server.isValidUrl()) {
view.showInvalidServerUrlMessage() view.showInvalidServerUrlMessage()
} else { } else {
launchUI(strategy) { launchUI(strategy) {
......
...@@ -3,20 +3,22 @@ package chat.rocket.android.authentication.signup.presentation ...@@ -3,20 +3,22 @@ package chat.rocket.android.authentication.signup.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.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.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.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.privacyPolicyUrl
import chat.rocket.android.util.extensions.registerPushToken import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.extensions.termsOfServiceUrl
import chat.rocket.android.util.retryIO
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.rest.login 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.signup import chat.rocket.core.internal.rest.signup
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import javax.inject.Inject import javax.inject.Inject
...@@ -60,10 +62,10 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -60,10 +62,10 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
try { try {
// TODO This function returns a user so should we save it? // TODO This function returns a user so should we save it?
client.signup(email, name, username, password) retryIO("signup") { client.signup(email, name, username, password) }
// TODO This function returns a user token so should we save it? // TODO This function returns a user token so should we save it?
client.login(username, password) retryIO("login") { client.login(username, password) }
val me = client.me() val me = retryIO("me") { client.me() }
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me) saveAccount(me)
registerPushToken() registerPushToken()
...@@ -88,13 +90,13 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -88,13 +90,13 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
fun termsOfService() { fun termsOfService() {
serverInteractor.get()?.let { serverInteractor.get()?.let {
navigator.toWebPage(UrlHelper.getTermsOfServiceUrl(it)) navigator.toWebPage(it.termsOfServiceUrl())
} }
} }
fun privacyPolicy() { fun privacyPolicy() {
serverInteractor.get()?.let { serverInteractor.get()?.let {
navigator.toWebPage(UrlHelper.getPrivacyPolicyUrl(it)) navigator.toWebPage(it.privacyPolicyUrl())
} }
} }
...@@ -108,12 +110,12 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -108,12 +110,12 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val logo = settings.wideTile()?.let { val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val thumb = UrlHelper.getAvatarUrl(currentServer, me.username!!) val thumb = currentServer.avatarUrl(me.username!!)
val account = Account(currentServer, icon, logo, me.username!!, thumb) val account = Account(currentServer, icon, logo, me.username!!, thumb)
saveAccountInteractor.save(account) saveAccountInteractor.save(account)
} }
......
...@@ -3,13 +3,15 @@ package chat.rocket.android.authentication.twofactor.presentation ...@@ -3,13 +3,15 @@ package chat.rocket.android.authentication.twofactor.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.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.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account 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.avatarUrl
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.registerPushToken import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -50,9 +52,10 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -50,9 +52,10 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
view.showLoading() view.showLoading()
try { try {
// The token is saved via the client TokenProvider // The token is saved via the client TokenProvider
val token = val token = retryIO("login") {
client.login(usernameOrEmail, password, twoFactorAuthenticationCode) client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
val me = client.me() }
val me = retryIO("me") { client.me() }
saveAccount(me) saveAccount(me)
tokenRepository.save(server, token) tokenRepository.save(server, token)
registerPushToken() registerPushToken()
...@@ -90,12 +93,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -90,12 +93,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val logo = settings.wideTile()?.let { val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val thumb = UrlHelper.getAvatarUrl(currentServer, me.username!!) val thumb = currentServer.avatarUrl(me.username!!)
val account = Account(currentServer, icon, logo, me.username!!, thumb) val account = Account(currentServer, icon, logo, me.username!!, thumb)
saveAccountInteractor.save(account) saveAccountInteractor.save(account)
} }
......
...@@ -92,8 +92,10 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -92,8 +92,10 @@ interface ChatRoomView : LoadingView, MessageView {
/** /**
* Enables the send message button. * Enables the send message button.
*
* @param sendFailed Whether the sent message has failed.
*/ */
fun enableSendMessageButton() fun enableSendMessageButton(sendFailed: Boolean)
/** /**
* Clears the message composition. * Clears the message composition.
......
...@@ -35,7 +35,6 @@ import kotlinx.android.synthetic.main.fragment_chat_room.* ...@@ -35,7 +35,6 @@ import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.* import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.* import kotlinx.android.synthetic.main.message_composer.*
import kotlinx.android.synthetic.main.message_list.* import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
...@@ -126,7 +125,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -126,7 +125,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
activity?.apply { activity?.apply {
(this as? ChatRoomActivity)?.showRoomTypeIcon(true) (this as? ChatRoomActivity)?.showRoomTypeIcon(true)
} }
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
...@@ -135,6 +133,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -135,6 +133,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
override fun onDestroyView() { override fun onDestroyView() {
recycler_view.removeOnScrollListener(endlessRecyclerViewScrollListener)
recycler_view.removeOnScrollListener(onScrollListener)
recycler_view.removeOnLayoutChangeListener(layoutChangeListener)
presenter.unsubscribeMessages(chatRoomId) presenter.unsubscribeMessages(chatRoomId)
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
unsubscribeTextMessage() unsubscribeTextMessage()
...@@ -203,58 +205,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -203,58 +205,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, presenter, adapter = ChatRoomAdapter(chatRoomType, chatRoomName, presenter,
reactionListener = this@ChatRoomFragment) reactionListener = this@ChatRoomFragment)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
linearLayoutManager.stackFromEnd = true
recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
if (dataSet.size >= 30) { if (dataSet.size >= 30) {
recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) { recycler_view.addOnScrollListener(endlessRecyclerViewScrollListener)
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
presenter.loadMessages(chatRoomId, chatRoomType, page * 30L)
}
})
}
recycler_view.addOnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom ->
val y = oldBottom - bottom
if (y.absoluteValue > 0) {
// if y is positive the keyboard is up else it's down
recycler_view.post {
if (y > 0 || verticalScrollOffset.get().absoluteValue >= y.absoluteValue) {
recycler_view.scrollBy(0, y)
} else {
recycler_view.scrollBy(0, verticalScrollOffset.get())
}
}
}
} }
recycler_view.addOnLayoutChangeListener(layoutChangeListener)
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() { recycler_view.addOnScrollListener(onScrollListener)
var state = AtomicInteger(RecyclerView.SCROLL_STATE_IDLE)
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
state.compareAndSet(RecyclerView.SCROLL_STATE_IDLE, newState)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
if (!state.compareAndSet(RecyclerView.SCROLL_STATE_SETTLING, newState)) {
state.compareAndSet(RecyclerView.SCROLL_STATE_DRAGGING, newState)
}
}
RecyclerView.SCROLL_STATE_DRAGGING -> {
state.compareAndSet(RecyclerView.SCROLL_STATE_IDLE, newState)
}
RecyclerView.SCROLL_STATE_SETTLING -> {
state.compareAndSet(RecyclerView.SCROLL_STATE_DRAGGING, newState)
}
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (state.get() != RecyclerView.SCROLL_STATE_IDLE) {
verticalScrollOffset.getAndAdd(dy)
}
}
})
} }
val oldMessagesCount = adapter.itemCount val oldMessagesCount = adapter.itemCount
...@@ -267,6 +222,61 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -267,6 +222,61 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
private val layoutChangeListener = View.OnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom ->
val y = oldBottom - bottom
if (y.absoluteValue > 0 && isAdded) {
// if y is positive the keyboard is up else it's down
recycler_view.post {
if (y > 0 || verticalScrollOffset.get().absoluteValue >= y.absoluteValue) {
recycler_view.scrollBy(0, y)
} else {
recycler_view.scrollBy(0, verticalScrollOffset.get())
}
}
}
}
private lateinit var endlessRecyclerViewScrollListener: EndlessRecyclerViewScrollListener
private val onScrollListener = object : RecyclerView.OnScrollListener() {
var state = AtomicInteger(RecyclerView.SCROLL_STATE_IDLE)
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
state.compareAndSet(RecyclerView.SCROLL_STATE_IDLE, newState)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
if (!state.compareAndSet(RecyclerView.SCROLL_STATE_SETTLING, newState)) {
state.compareAndSet(RecyclerView.SCROLL_STATE_DRAGGING, newState)
}
}
RecyclerView.SCROLL_STATE_DRAGGING -> {
state.compareAndSet(RecyclerView.SCROLL_STATE_IDLE, newState)
}
RecyclerView.SCROLL_STATE_SETTLING -> {
state.compareAndSet(RecyclerView.SCROLL_STATE_DRAGGING, newState)
}
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (state.get() != RecyclerView.SCROLL_STATE_IDLE) {
verticalScrollOffset.getAndAdd(dy)
}
}
}
private val fabScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (!recyclerView.canScrollVertically(1)) {
button_fab.hide()
} else {
if (dy < 0 && !button_fab.isVisible()) {
button_fab.show()
}
}
}
}
override fun sendMessage(text: String) { override fun sendMessage(text: String) {
if (!text.isBlank()) { if (!text.isBlank()) {
if (!text.startsWith("/")) { if (!text.startsWith("/")) {
...@@ -296,10 +306,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -296,10 +306,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_send.isEnabled = false button_send.isEnabled = false
} }
override fun enableSendMessageButton() { override fun enableSendMessageButton(sendFailed: Boolean) {
button_send.isEnabled = true button_send.isEnabled = true
text_message.isEnabled = true text_message.isEnabled = true
text_message.erase() if (!sendFailed) {
clearMessageComposition()
}
} }
override fun clearMessageComposition() { override fun clearMessageComposition() {
...@@ -463,18 +475,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -463,18 +475,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() { // Initialize the endlessRecyclerViewScrollListener so we don't NPE at onDestroyView
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
Timber.i("Scrolling vertically: $dy") linearLayoutManager.stackFromEnd = true
if (!recyclerView.canScrollVertically(1)) { recycler_view.layoutManager = linearLayoutManager
button_fab.hide() recycler_view.itemAnimator = DefaultItemAnimator()
} else { endlessRecyclerViewScrollListener = object :
if (dy < 0 && !button_fab.isVisible()) { EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) {
button_fab.show() override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
} presenter.loadMessages(chatRoomId, chatRoomType, page * 30L)
}
} }
}) }
recycler_view.addOnScrollListener(fabScrollListener)
} }
private fun setupFab() { private fun setupFab() {
...@@ -522,8 +534,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -522,8 +534,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
var textMessage = citation ?: "" var textMessage = citation ?: ""
textMessage += text_message.textContent textMessage += text_message.textContent
sendMessage(textMessage) sendMessage(textMessage)
clearMessageComposition()
} }
button_show_attachment_options.setOnClickListener { button_show_attachment_options.setOnClickListener {
...@@ -588,7 +598,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -588,7 +598,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
text_message.requestFocus() text_message.requestFocus()
emojiKeyboardPopup.showAtBottomPending() emojiKeyboardPopup.showAtBottomPending()
KeyboardHelper.showSoftKeyboard(text_message) KeyboardHelper.showSoftKeyboard(text_message)
} }
setReactionButtonIcon(R.drawable.ic_keyboard_black_24dp) setReactionButtonIcon(R.drawable.ic_keyboard_black_24dp)
} else { } else {
......
...@@ -9,9 +9,9 @@ import android.text.style.ForegroundColorSpan ...@@ -9,9 +9,9 @@ import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType import chat.rocket.core.model.MessageType
...@@ -235,7 +235,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -235,7 +235,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val username = message.sender?.username ?: "?" val username = message.sender?.username ?: "?"
return baseUrl?.let { return baseUrl?.let {
UrlHelper.getAvatarUrl(baseUrl, username) baseUrl.avatarUrl(username)
} }
} }
......
...@@ -12,6 +12,7 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory ...@@ -12,6 +12,7 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.chatRooms import chat.rocket.android.server.infraestructure.chatRooms
import chat.rocket.android.server.infraestructure.state import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.BaseRoom import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
...@@ -94,7 +95,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -94,7 +95,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
try { try {
val roomList = getChatRoomsInteractor.getByName(currentServer, name) val roomList = getChatRoomsInteractor.getByName(currentServer, name)
if (roomList.isEmpty()) { if (roomList.isEmpty()) {
val (users, rooms) = client.spotlight(name) val (users, rooms) = retryIO("spotlight($name)") {
client.spotlight(name)
}
val chatRoomsCombined = mutableListOf<ChatRoom>() val chatRoomsCombined = mutableListOf<ChatRoom>()
chatRoomsCombined.addAll(usersToChatRooms(users)) chatRoomsCombined.addAll(usersToChatRooms(users))
chatRoomsCombined.addAll(roomsToChatRooms(rooms)) chatRoomsCombined.addAll(roomsToChatRooms(rooms))
...@@ -161,7 +164,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -161,7 +164,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
} }
private suspend fun loadRooms(): List<ChatRoom> { private suspend fun loadRooms(): List<ChatRoom> {
val chatRooms = manager.chatRooms().update val chatRooms = retryIO("chatRooms") { manager.chatRooms().update }
val sortedRooms = sortRooms(chatRooms) val sortedRooms = sortRooms(chatRooms)
Timber.d("Loaded rooms: ${sortedRooms.size}") Timber.d("Loaded rooms: ${sortedRooms.size}")
saveChatRoomsInteractor.save(currentServer, sortedRooms) saveChatRoomsInteractor.save(currentServer, sortedRooms)
......
...@@ -12,11 +12,11 @@ import android.view.View ...@@ -12,11 +12,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.checkIfMyself import chat.rocket.android.infrastructure.checkIfMyself
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.content import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible import chat.rocket.android.util.extensions.setVisible
...@@ -75,7 +75,7 @@ class ChatRoomsAdapter(private val context: Context, ...@@ -75,7 +75,7 @@ class ChatRoomsAdapter(private val context: Context,
private fun bindAvatar(chatRoom: ChatRoom, drawee: SimpleDraweeView) { private fun bindAvatar(chatRoom: ChatRoom, drawee: SimpleDraweeView) {
val avatarId = if (chatRoom.type is RoomType.DirectMessage) chatRoom.name else "@${chatRoom.name}" val avatarId = if (chatRoom.type is RoomType.DirectMessage) chatRoom.name else "@${chatRoom.name}"
drawee.setImageURI(UrlHelper.getAvatarUrl(chatRoom.client.url, avatarId)) drawee.setImageURI(chatRoom.client.url.avatarUrl(avatarId))
} }
private fun bindName(chatRoom: ChatRoom, textView: TextView) { private fun bindName(chatRoom: ChatRoom, textView: TextView) {
......
...@@ -158,15 +158,16 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -158,15 +158,16 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
listJob?.cancel() listJob?.cancel()
listJob = launch(UI) { listJob = launch(UI) {
val adapter = recycler_view.adapter as SimpleSectionedRecyclerViewAdapter val adapter = recycler_view.adapter as SimpleSectionedRecyclerViewAdapter
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android.dev/issues/5a90d4718cb3c2fa63b3f557?time=last-seven-days // FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android/issues/5ac2916c36c7b235275ccccf
// TODO - fix this bug to reenable DiffUtil // TODO - fix this bug to re-enable DiffUtil
val diff = async(CommonPool) { /*val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet)) DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await() }.await()*/
if (isActive) { if (isActive) {
adapter.baseAdapter.updateRooms(newDataSet) adapter.baseAdapter.updateRooms(newDataSet)
diff.dispatchUpdatesTo(adapter) // TODO - fix crash to re-enable diff.dispatchUpdatesTo(adapter)
adapter.notifyDataSetChanged()
//Set sections always after data set is updated //Set sections always after data set is updated
setSections() setSections()
......
package chat.rocket.android.helper package chat.rocket.android.helper
import chat.rocket.android.util.extensions.removeTrailingSlash
object OauthHelper { object OauthHelper {
/** /**
...@@ -27,7 +29,7 @@ object OauthHelper { ...@@ -27,7 +29,7 @@ object OauthHelper {
fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String): String { fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://accounts.google.com/o/oauth2/v2/auth" + return "https://accounts.google.com/o/oauth2/v2/auth" +
"?client_id=$clientId" + "?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/google?close" + "&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/google?close" +
"&state=$state" + "&state=$state" +
"&response_type=code" + "&response_type=code" +
"&scope=email%20profile" "&scope=email%20profile"
...@@ -44,7 +46,7 @@ object OauthHelper { ...@@ -44,7 +46,7 @@ object OauthHelper {
fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String): String { fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://linkedin.com/oauth/v2/authorization" + return "https://linkedin.com/oauth/v2/authorization" +
"?client_id=$clientId" + "?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/linkedin?close" + "&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/linkedin?close" +
"&state=$state" + "&state=$state" +
"&response_type=code" "&response_type=code"
} }
...@@ -60,7 +62,7 @@ object OauthHelper { ...@@ -60,7 +62,7 @@ object OauthHelper {
fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String { fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://gitlab.com/oauth/authorize" + return "https://gitlab.com/oauth/authorize" +
"?client_id=$clientId" + "?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/gitlab?close" + "&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/gitlab?close" +
"&state=$state" + "&state=$state" +
"&response_type=code" + "&response_type=code" +
"&scope=read_user" "&scope=read_user"
......
package chat.rocket.android.helper
import android.util.Patterns
object UrlHelper {
/**
* Returns the avatar URL.
*
* @param serverUrl The server URL.
* @param avatarName The avatar name.
* @return The avatar URL.
*/
fun getAvatarUrl(serverUrl: String, avatarName: String, format: String = "jpeg"): String =
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.
*
* @param casLoginUrl The CAS login URL from the server settings.
* @param serverUrl The server URL.
* @param token The token to be send to the CAS server.
* @return The avatar URL.
*/
fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String =
removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token
/**
* Returns the server's Terms of Service URL.
*
* @param serverUrl The server URL.
* @return The server's Terms of Service URL.
*/
fun getTermsOfServiceUrl(serverUrl: String) = removeTrailingSlash(serverUrl) + "/terms-of-service"
/**
* Returns the server's Privacy Policy URL.
*
* @param serverUrl The server URL.
* @return The server's Privacy Policy URL.
*/
fun getPrivacyPolicyUrl(serverUrl: String) = removeTrailingSlash(serverUrl) + "/privacy-policy"
/**
* Returns an URL without trailing slash.
*
* @param serverUrl The URL to remove the trailing slash (if exists).
* @return An URL without trailing slash.
*/
fun removeTrailingSlash(serverUrl: String): String {
return if (serverUrl[serverUrl.length - 1] == '/') {
serverUrl.replace("/+$", "")
} else {
serverUrl
}
}
/**
* Checks if the given URL is valid or not.
* @param url The url to check its valid.
* @return True if url is valid, false otherwise.
*/
fun isValidUrl(url: String): Boolean = Patterns.WEB_URL.matcher(url).matches()
}
\ No newline at end of file
...@@ -6,6 +6,7 @@ import chat.rocket.android.server.domain.model.Account ...@@ -6,6 +6,7 @@ import chat.rocket.android.server.domain.model.Account
import kotlinx.android.synthetic.main.item_account.view.* import kotlinx.android.synthetic.main.item_account.view.*
class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(account: Account) { fun bind(account: Account) {
with(itemView) { with(itemView) {
server_logo.setImageURI(account.serverLogo) server_logo.setImageURI(account.serverLogo)
......
...@@ -5,50 +5,64 @@ import android.view.ViewGroup ...@@ -5,50 +5,64 @@ import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.core.internal.realtime.UserStatus
private const val VIEW_TYPE_CHANGE_STATUS = 0
private const val VIEW_TYPE_ACCOUNT = 1
private const val VIEW_TYPE_ADD_ACCOUNT = 2
class AccountsAdapter( class AccountsAdapter(
private val accounts: List<Account>, private val accounts: List<Account>,
private val selector: AccountSelector private val selector: Selector
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
VIEW_TYPE_CHANGE_STATUS -> StatusViewHolder(parent.inflate(R.layout.item_change_status))
VIEW_TYPE_ACCOUNT -> AccountViewHolder(parent.inflate(R.layout.item_account)) VIEW_TYPE_ACCOUNT -> AccountViewHolder(parent.inflate(R.layout.item_account))
else -> AddAccountViewHolder(parent.inflate(R.layout.item_add_account)) else -> AddAccountViewHolder(parent.inflate(R.layout.item_add_account))
} }
} }
override fun getItemCount() = accounts.size + 1 override fun getItemCount() = accounts.size + 2
override fun getItemViewType(position: Int) = override fun getItemViewType(position: Int): Int {
if (position == accounts.size) VIEW_TYPE_ADD_ACCOUNT else VIEW_TYPE_ACCOUNT return when {
position == 0 -> VIEW_TYPE_CHANGE_STATUS
position <= accounts.size -> VIEW_TYPE_ACCOUNT
else -> VIEW_TYPE_ADD_ACCOUNT
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) { when (holder) {
is StatusViewHolder -> bindStatusViewHolder(holder)
is AccountViewHolder -> bindAccountViewHolder(holder, position) is AccountViewHolder -> bindAccountViewHolder(holder, position)
is AddAccountViewHolder -> bindAddAccountViewHolder(holder, position) is AddAccountViewHolder -> bindAddAccountViewHolder(holder)
} }
} }
private fun bindStatusViewHolder(holder: StatusViewHolder) {
holder.bind { userStatus -> selector.onStatusSelected(userStatus) }
}
private fun bindAccountViewHolder(holder: AccountViewHolder, position: Int) { private fun bindAccountViewHolder(holder: AccountViewHolder, position: Int) {
val account = accounts[position] val account = accounts[position - 1]
holder.bind(account) holder.bind(account)
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
selector.onAccountSelected(account.serverUrl) selector.onAccountSelected(account.serverUrl)
} }
} }
private fun bindAddAccountViewHolder(holder: AddAccountViewHolder, position: Int) { private fun bindAddAccountViewHolder(holder: AddAccountViewHolder) {
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
selector.onAddedAccountSelected() selector.onAddedAccountSelected()
} }
} }
} }
interface AccountSelector { interface Selector {
fun onStatusSelected(userStatus: UserStatus)
fun onAccountSelected(serverUrl: String) fun onAccountSelected(serverUrl: String)
fun onAddedAccountSelected() fun onAddedAccountSelected()
} }
\ No newline at end of file
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
import chat.rocket.core.internal.realtime.UserStatus
import kotlinx.android.synthetic.main.item_change_status.view.*
class StatusViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(listener: (UserStatus) -> Unit) {
with(itemView) {
text_online.setOnClickListener { listener(UserStatus.Online) }
text_away.setOnClickListener { listener(UserStatus.Away) }
text_busy.setOnClickListener { listener(UserStatus.Busy) }
text_invisible.setOnClickListener { listener(UserStatus.Offline) }
}
}
}
\ 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.main.viewmodel.NavHeaderViewModel import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.main.viewmodel.NavHeaderViewModelMapper import chat.rocket.android.main.viewmodel.NavHeaderViewModelMapper
...@@ -12,10 +11,14 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -12,10 +11,14 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.registerPushToken import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
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.UserStatus
import chat.rocket.core.internal.realtime.setDefaultStatus
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.me
import chat.rocket.core.internal.rest.unregisterPushToken import chat.rocket.core.internal.rest.unregisterPushToken
...@@ -52,9 +55,8 @@ class MainPresenter @Inject constructor( ...@@ -52,9 +55,8 @@ class MainPresenter @Inject constructor(
checkServerInfo() checkServerInfo()
launchUI(strategy) { launchUI(strategy) {
try { try {
val me = client.me() val me = retryIO("me") { client.me() }
val model = navHeaderMapper.mapToViewModel(me) val model = navHeaderMapper.mapToViewModel(me)
saveAccount(model) saveAccount(model)
view.setupNavHeader(model, getAccountsInteractor.get()) view.setupNavHeader(model, getAccountsInteractor.get())
} catch (ex: Exception) { } catch (ex: Exception) {
...@@ -75,11 +77,17 @@ class MainPresenter @Inject constructor( ...@@ -75,11 +77,17 @@ class MainPresenter @Inject constructor(
} }
} }
private suspend fun saveAccount(me: NavHeaderViewModel) { private suspend fun saveAccount(viewModel: NavHeaderViewModel) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) currentServer.serverLogoUrl(it)
} }
val account = Account(currentServer, icon, me.serverLogo, me.username, me.avatar) val account = Account(
currentServer,
icon,
viewModel.serverLogo,
viewModel.userDisplayName,
viewModel.userAvatar
)
saveAccountInteractor.save(account) saveAccountInteractor.save(account)
} }
...@@ -90,7 +98,7 @@ class MainPresenter @Inject constructor( ...@@ -90,7 +98,7 @@ class MainPresenter @Inject constructor(
launchUI(strategy) { launchUI(strategy) {
try { try {
clearTokens() clearTokens()
client.logout() retryIO("logout") { client.logout() }
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
Timber.d(exception, "Error calling logout") Timber.d(exception, "Error calling logout")
exception.message?.let { exception.message?.let {
...@@ -117,7 +125,11 @@ class MainPresenter @Inject constructor( ...@@ -117,7 +125,11 @@ class MainPresenter @Inject constructor(
serverInteractor.clear() serverInteractor.clear()
val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN) val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN)
if (pushToken != null) { if (pushToken != null) {
client.unregisterPushToken(pushToken) try {
retryIO("unregisterPushToken") { client.unregisterPushToken(pushToken) }
} catch (ex: Exception) {
Timber.d(ex, "Error unregistering push token")
}
} }
localRepository.clearAllFromServer(currentServer) localRepository.clearAllFromServer(currentServer)
} }
...@@ -142,6 +154,21 @@ class MainPresenter @Inject constructor( ...@@ -142,6 +154,21 @@ class MainPresenter @Inject constructor(
navigator.toServerScreen() navigator.toServerScreen()
} }
fun changeStatus(userStatus: UserStatus) {
launchUI(strategy) {
try {
client.setDefaultStatus(userStatus)
view.showUserStatus(userStatus)
} catch (ex: RocketChatException) {
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
suspend fun refreshToken(token: String?) { suspend fun refreshToken(token: String?) {
token?.let { token?.let {
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, token) localRepository.save(LocalRepository.KEY_PUSH_TOKEN, token)
......
...@@ -4,8 +4,24 @@ import chat.rocket.android.authentication.server.presentation.VersionCheckView ...@@ -4,8 +4,24 @@ import chat.rocket.android.authentication.server.presentation.VersionCheckView
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.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.core.internal.realtime.UserStatus
interface MainView : MessageView, VersionCheckView { interface MainView : MessageView, VersionCheckView {
fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>)
/**
* Shows the current user status.
*
* @see [UserStatus]
*/
fun showUserStatus(userStatus: UserStatus)
/**
* Setups the navigation header.
*
* @param viewModel The [NavHeaderViewModel].
* @param accounts The list of accounts.
*/
fun setupNavHeader(viewModel: NavHeaderViewModel, accounts: List<Account>)
fun closeServerSelection() fun closeServerSelection()
} }
\ No newline at end of file
...@@ -11,7 +11,7 @@ import android.view.MenuItem ...@@ -11,7 +11,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.main.adapter.AccountSelector import chat.rocket.android.main.adapter.Selector
import chat.rocket.android.main.adapter.AccountsAdapter 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
...@@ -21,6 +21,7 @@ import chat.rocket.android.util.extensions.fadeIn ...@@ -21,6 +21,7 @@ import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.rotateBy import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.core.internal.realtime.UserStatus
import com.google.android.gms.gcm.GoogleCloudMessaging import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID import com.google.android.gms.iid.InstanceID
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
...@@ -42,6 +43,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -42,6 +43,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
@Inject lateinit var presenter: MainPresenter @Inject lateinit var presenter: MainPresenter
private var isFragmentAdded: Boolean = false private var isFragmentAdded: Boolean = false
private var expanded = false private var expanded = false
private val headerLayout by lazy { view_navigation.getHeaderView(0) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this) AndroidInjection.inject(this)
...@@ -79,14 +81,32 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -79,14 +81,32 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
} }
} }
override fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>) { override fun showUserStatus(userStatus: UserStatus) {
Timber.d("Setting up nav header: $model") headerLayout.apply {
val headerLayout = view_navigation.getHeaderView(0) image_user_status.setImageDrawable(
headerLayout.text_name.text = model.username DrawableHelper.getUserStatusDrawable(
headerLayout.text_server.text = model.server userStatus,
headerLayout.image_avatar.setImageURI(model.avatar) this.context
headerLayout.server_logo.setImageURI(model.serverLogo) )
setupAccountsList(headerLayout, accounts) )
}
}
override fun setupNavHeader(viewModel: NavHeaderViewModel, accounts: List<Account>) {
Timber.d("Setting up nav header: $viewModel")
with(headerLayout) {
image_user_status.setImageDrawable(
DrawableHelper.getUserStatusDrawable(
viewModel.userStatus!!,
this.context
)
)
text_user_name.text = viewModel.userDisplayName
text_server_url.text = viewModel.serverUrl
image_avatar.setImageURI(viewModel.userAvatar)
server_logo.setImageURI(viewModel.serverLogo)
setupAccountsList(headerLayout, accounts)
}
} }
override fun closeServerSelection() { override fun closeServerSelection() {
...@@ -112,7 +132,11 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -112,7 +132,11 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
private fun setupAccountsList(header: View, accounts: List<Account>) { private fun setupAccountsList(header: View, accounts: List<Account>) {
accounts_list.layoutManager = LinearLayoutManager(this) accounts_list.layoutManager = LinearLayoutManager(this)
accounts_list.adapter = AccountsAdapter(accounts, object : AccountSelector { accounts_list.adapter = AccountsAdapter(accounts, object : Selector {
override fun onStatusSelected(userStatus: UserStatus) {
presenter.changeStatus(userStatus)
}
override fun onAccountSelected(serverUrl: String) { override fun onAccountSelected(serverUrl: String) {
presenter.changeServer(serverUrl) presenter.changeServer(serverUrl)
} }
...@@ -120,11 +144,10 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -120,11 +144,10 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
override fun onAddedAccountSelected() { override fun onAddedAccountSelected() {
presenter.addNewServer() presenter.addNewServer()
} }
}) })
header.account_container.setOnClickListener { header.account_container.setOnClickListener {
header.account_expand.rotateBy(180f) header.image_account_expand.rotateBy(180f)
if (expanded) { if (expanded) {
accounts_list.fadeOut() accounts_list.fadeOut()
} else { } else {
......
package chat.rocket.android.main.viewmodel package chat.rocket.android.main.viewmodel
import chat.rocket.common.model.UserStatus
data class NavHeaderViewModel( data class NavHeaderViewModel(
val username: String, val userDisplayName: String,
val server: String, val userStatus: UserStatus?,
val avatar: String?, val userAvatar: String?,
val serverUrl: String,
val serverLogo: String? val serverLogo: String?
) )
\ No newline at end of file
package chat.rocket.android.main.viewmodel package chat.rocket.android.main.viewmodel
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import javax.inject.Inject import javax.inject.Inject
class NavHeaderViewModelMapper @Inject constructor(serverInteractor: GetCurrentServerInteractor, class NavHeaderViewModelMapper @Inject constructor(
getSettingsInteractor: GetSettingsInteractor) { serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor
) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private var settings: PublicSettings = getSettingsInteractor.get(currentServer) private var settings: PublicSettings = getSettingsInteractor.get(currentServer)
fun mapToViewModel(me: Myself): NavHeaderViewModel { fun mapToViewModel(me: Myself): NavHeaderViewModel {
val username = mapUsername(me) val displayName = mapDisplayName(me)
val thumb = me.username?.let { UrlHelper.getAvatarUrl(currentServer, it) } val status = me.status
val avatar = me.username?.let { currentServer.avatarUrl(it) }
val image = settings.wideTile() ?: settings.faviconLarge() val image = settings.wideTile() ?: settings.faviconLarge()
val logo = image?.let { UrlHelper.getServerLogoUrl(currentServer, it) } val logo = image?.let { currentServer.serverLogoUrl(it) }
return NavHeaderViewModel(username, currentServer, thumb, logo) return NavHeaderViewModel(displayName, status, avatar, currentServer, logo)
} }
private fun mapUsername(me: Myself): String { private fun mapDisplayName(me: Myself): String {
val username = me.username val username = me.username
val realName = me.name val realName = me.name
val senderName = if (settings.useRealName()) realName else username val senderName = if (settings.useRealName()) realName else username
......
...@@ -6,6 +6,7 @@ import chat.rocket.android.members.viewmodel.MemberViewModelMapper ...@@ -6,6 +6,7 @@ import chat.rocket.android.members.viewmodel.MemberViewModelMapper
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
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.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -26,7 +27,9 @@ class MembersPresenter @Inject constructor(private val view: MembersView, ...@@ -26,7 +27,9 @@ class MembersPresenter @Inject constructor(private val view: MembersView,
try { try {
view.showLoading() view.showLoading()
val members = client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 60) val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") {
client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 60)
}
val memberViewModels = mapper.mapToViewModelList(members.result) val memberViewModels = mapper.mapToViewModelList(members.result)
view.showMembers(memberViewModels, members.total) view.showMembers(memberViewModels, members.total)
} catch (ex: RocketChatException) { } catch (ex: RocketChatException) {
......
package chat.rocket.android.members.viewmodel package chat.rocket.android.members.viewmodel
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.common.model.User import chat.rocket.common.model.User
import chat.rocket.core.model.Value import chat.rocket.core.model.Value
...@@ -25,7 +25,7 @@ class MemberViewModel(private val member: User, private val settings: Map<String ...@@ -25,7 +25,7 @@ class MemberViewModel(private val member: User, private val settings: Map<String
private fun getUserAvatar(): String? { private fun getUserAvatar(): String? {
val username = member.username ?: "?" val username = member.username ?: "?"
return baseUrl?.let { return baseUrl?.let {
UrlHelper.getAvatarUrl(baseUrl, username, "png") baseUrl.avatarUrl(username,"png")
} }
} }
......
package chat.rocket.android.profile.presentation package chat.rocket.android.profile.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.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
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
...@@ -25,9 +26,9 @@ class ProfilePresenter @Inject constructor(private val view: ProfileView, ...@@ -25,9 +26,9 @@ class ProfilePresenter @Inject constructor(private val view: ProfileView,
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
val myself = client.me() val myself = retryIO("me") { client.me() }
myselfId = myself.id myselfId = myself.id
val avatarUrl = UrlHelper.getAvatarUrl(serverUrl, myself.username!!) val avatarUrl = serverUrl.avatarUrl(myself.username!!)
view.showProfile( view.showProfile(
avatarUrl, avatarUrl,
myself.name ?: "", myself.name ?: "",
...@@ -51,9 +52,9 @@ class ProfilePresenter @Inject constructor(private val view: ProfileView, ...@@ -51,9 +52,9 @@ class ProfilePresenter @Inject constructor(private val view: ProfileView,
view.showLoading() view.showLoading()
try { try {
if(avatarUrl!="") { if(avatarUrl!="") {
client.setAvatar(avatarUrl) retryIO { client.setAvatar(avatarUrl) }
} }
val user = client.updateProfile(myselfId, email, name, username) val user = retryIO { client.updateProfile(myselfId, email, name, username) }
view.showProfileUpdateSuccessfullyMessage() view.showProfileUpdateSuccessfullyMessage()
loadUserProfile() loadUserProfile()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
......
...@@ -5,7 +5,6 @@ import chat.rocket.android.infrastructure.LocalRepository ...@@ -5,7 +5,6 @@ import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import com.google.android.gms.gcm.GoogleCloudMessaging import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID import com.google.android.gms.iid.InstanceID
......
...@@ -5,6 +5,7 @@ import chat.rocket.android.authentication.server.presentation.VersionCheckView ...@@ -5,6 +5,7 @@ import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.VersionInfo import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.serverInfo import chat.rocket.core.internal.rest.serverInfo
import timber.log.Timber import timber.log.Timber
...@@ -14,21 +15,25 @@ abstract class CheckServerPresenter constructor(private val strategy: CancelStra ...@@ -14,21 +15,25 @@ abstract class CheckServerPresenter constructor(private val strategy: CancelStra
private val view: VersionCheckView) { private val view: VersionCheckView) {
internal fun checkServerInfo() { internal fun checkServerInfo() {
launchUI(strategy) { launchUI(strategy) {
val serverInfo = client.serverInfo() try {
val thisServerVersion = serverInfo.version val serverInfo = retryIO(description = "serverInfo", times = 5) { client.serverInfo() }
val isRequiredVersion = isRequiredServerVersion(thisServerVersion) val thisServerVersion = serverInfo.version
val isRecommendedVersion = isRecommendedServerVersion(thisServerVersion) val isRequiredVersion = isRequiredServerVersion(thisServerVersion)
if (isRequiredVersion) { val isRecommendedVersion = isRecommendedServerVersion(thisServerVersion)
if (isRecommendedVersion) { if (isRequiredVersion) {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: $thisServerVersion)") if (isRecommendedVersion) {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: $thisServerVersion)")
} else {
view.alertNotRecommendedVersion()
}
} else { } else {
view.alertNotRecommendedVersion() if (!isRecommendedVersion) {
} view.blockAndAlertNotRequiredVersion()
} else { Timber.i("Oops. Looks like your server is out-of-date! Minimum server version required ${BuildConfig.REQUIRED_SERVER_VERSION}!")
if (!isRecommendedVersion) { }
view.blockAndAlertNotRequiredVersion()
Timber.i("Oops. Looks like your server is out-of-date! Minimum server version required ${BuildConfig.REQUIRED_SERVER_VERSION}!")
} }
} catch (ex: Exception) {
Timber.d(ex, "Error getting server info")
} }
} }
} }
......
package chat.rocket.android.settings.about.ui
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.util.extensions.textContent
import kotlinx.android.synthetic.main.about_view.*
import kotlinx.android.synthetic.main.app_bar_password.*
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
setupToolbar()
setupViews()
}
private fun setupViews() {
val versionName = resources.getString(R.string.msg_version) +" "+BuildConfig.VERSION_NAME
val versionCode = resources.getString(R.string.msg_build)+" #"+ BuildConfig.VERSION_CODE
text_version_name.text = versionName
text_build_number.text = versionCode
}
private fun setupToolbar() {
setSupportActionBar(toolbar)
text_change_password.textContent = resources.getString(R.string.title_about)
}
override fun onBackPressed() {
super.onBackPressed()
finish()
overridePendingTransition(R.anim.close_enter, R.anim.close_exit)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return super.onNavigateUp()
}
}
...@@ -4,6 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -4,6 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
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.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
...@@ -22,7 +23,10 @@ class PasswordPresenter @Inject constructor (private val view: PasswordView, ...@@ -22,7 +23,10 @@ class PasswordPresenter @Inject constructor (private val view: PasswordView,
try { try {
view.showLoading() view.showLoading()
client.updateProfile(client.me().id, null, null, password, null) val me = retryIO("me") { client.me() }
retryIO("updateProfile(${me.id})") {
client.updateProfile(me.id, null, null, password, null)
}
view.showPasswordSuccessfullyUpdatedMessage() view.showPasswordSuccessfullyUpdatedMessage()
view.hideLoading() view.hideLoading()
......
...@@ -10,6 +10,7 @@ import android.view.ViewGroup ...@@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.settings.about.ui.AboutActivity
import chat.rocket.android.settings.password.ui.PasswordActivity import chat.rocket.android.settings.password.ui.PasswordActivity
import chat.rocket.android.settings.presentation.SettingsView import chat.rocket.android.settings.presentation.SettingsView
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
...@@ -33,8 +34,10 @@ class SettingsFragment: Fragment(), SettingsView, AdapterView.OnItemClickListene ...@@ -33,8 +34,10 @@ class SettingsFragment: Fragment(), SettingsView, AdapterView.OnItemClickListene
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when (parent?.getItemAtPosition(position).toString()) { when (parent?.getItemAtPosition(position).toString()) {
"Change Password" -> { resources.getString(R.string.title_password) -> {
startNewActivity(PasswordActivity::class) startNewActivity(PasswordActivity::class)
}resources.getString(R.string.title_about) -> {
startNewActivity(AboutActivity::class)
} }
} }
} }
......
package chat.rocket.android.util
import chat.rocket.common.RocketChatNetworkErrorException
import kotlinx.coroutines.experimental.delay
import timber.log.Timber
const val DEFAULT_RETRY = 3
suspend fun <T> retryIO(
description: String = "<missing description>",
times: Int = DEFAULT_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1000, // 1 second
factor: Double = 2.0,
block: suspend () -> T): T
{
var currentDelay = initialDelay
repeat(times - 1) { currentTry ->
try {
return block()
} catch (e: RocketChatNetworkErrorException) {
Timber.d(e, "failed call($currentTry): $description")
e.printStackTrace()
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
return block() // last attempt
}
\ No newline at end of file
...@@ -2,6 +2,7 @@ package chat.rocket.android.util.extensions ...@@ -2,6 +2,7 @@ package chat.rocket.android.util.extensions
import chat.rocket.android.server.domain.model.Account 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.retryIO
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
...@@ -16,7 +17,9 @@ suspend fun RocketChatClient.registerPushToken( ...@@ -16,7 +17,9 @@ suspend fun RocketChatClient.registerPushToken(
launch(CommonPool) { launch(CommonPool) {
accounts.forEach { account -> accounts.forEach { account ->
try { try {
factory.create(account.serverUrl).registerPushToken(token) retryIO(description = "register push token: ${account.serverUrl}") {
factory.create(account.serverUrl).registerPushToken(token)
}
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error registering Push token for ${account.serverUrl}") Timber.d(ex, "Error registering Push token for ${account.serverUrl}")
ex.printStackTrace() ex.printStackTrace()
......
package chat.rocket.android.util.extensions
import android.util.Patterns
fun String.removeTrailingSlash(): String {
return if (isNotEmpty() && this[length - 1] == '/') {
this.replace("/+$", "")
} else {
this
}
}
fun String.avatarUrl(avatar: String, format: String = "jpeg") =
"${removeTrailingSlash()}/avatar/${avatar.removeTrailingSlash()}?format=$format"
fun String.serverLogoUrl(favicon: String) = "${removeTrailingSlash()}/$favicon"
fun String.casUrl(serverUrl: String, token: String) =
"${removeTrailingSlash()}?service=${serverUrl.removeTrailingSlash()}/_cas/$token"
fun String.termsOfServiceUrl() = "${removeTrailingSlash()}/terms-of-service"
fun String.privacyPolicyUrl() = "${removeTrailingSlash()}/privacy-policy"
fun String.isValidUrl(): Boolean = Patterns.WEB_URL.matcher(this).matches()
\ No newline at end of file
...@@ -29,14 +29,14 @@ fun Uri.getFileName(context: Context): String? { ...@@ -29,14 +29,14 @@ fun Uri.getFileName(context: Context): String? {
fun Uri.getFileSize(context: Context): Int { fun Uri.getFileSize(context: Context): Int {
val cursor = context.contentResolver.query(this, null, null, null, null, null) val cursor = context.contentResolver.query(this, null, null, null, null, null)
var fileSize: String? = null val fileSize = cursor?.use {
cursor.use { cursor ->
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
if (cursor != null && cursor.moveToFirst()) { if (cursor.moveToFirst()) {
if (!cursor.isNull(sizeIndex)) { if (!cursor.isNull(sizeIndex)) {
fileSize = cursor.getString(sizeIndex) return@use cursor.getString(sizeIndex)
} }
} }
return@use null
} }
return fileSize?.toIntOrNull() ?: -1 return fileSize?.toIntOrNull() ?: -1
} }
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape <shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<gradient <gradient
android:angle="90" android:angle="90"
android:endColor="#00000000"
android:centerColor="#30000000" android:centerColor="#30000000"
android:endColor="#00000000"
android:startColor="#C0000000" android:startColor="#C0000000"
android:type="linear" /> android:type="linear" />
</shape> </shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="832.000000dp"
android:height="220.000000dp"
android:viewportWidth="832.000000"
android:viewportHeight="220.000000">
<group
android:translateY="220.000000"
android:scaleX="0.100000"
android:scaleY="-0.100000">
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M65 1778 c-3 -7 -4 -308 -3 -668 l3 -655 104 -3 c67 -2 108 1 117 9 11 9 14 54 14 220 l0 209 49 0 49 0 16 -52 c8 -29 28 -107 45 -173 17 -66 36 -140 43 -165 l14 -45 111 -3 c129 -3 129 -3 106 85 -8 32 -21 83 -28 113 -7 30 -21 82 -30 115 -41 151 -41 145 -6 172 17 14 42 44 54 66 21 41 22 51 22 342 l0 300 -25 45 c-18 32 -39 51 -79 72 l-53 28 -259 0 c-199 0 -261 -3 -264 -12z m435 -238 c5 -8 10 -97 10 -197 0 -152 -3 -184 -16 -197 -12 -12 -37 -16 -105 -16 l-89 0 0 216 0 216 95 -4 c71 -2 98 -7 105 -18z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M1006 1774 c-48 -17 -70 -36 -105 -89 l-26 -40 0 -521 0 -521 30 -49 c22 -36 44 -56 80 -74 48 -24 58 -25 225 -25 169 0 177 1 225 26 35 18 59 39 80 74 l30 48 3 501 c2 448 1 506 -14 546 -22 60 -67 106 -122 125 -62 21 -349 20 -406 -1z m299 -230 c14 -14 15 -61 13 -430 -3 -356 -5 -416 -18 -424 -26 -17 -156 -12 -174 6 -14 13 -16 66 -16 428 0 307 3 415 12 424 19 19 163 16 183 -4z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M1835 1774 c-45 -17 -77 -44 -108 -94 -22 -35 -22 -38 -22 -555 0 -476 1 -523 17 -552 55 -100 114 -123 318 -123 166 0 229 15 276 65 49 53 58 83 62 217 2 70 1 139 -2 153 l-7 26 -107 -3 -107 -3 -6 -90 c-8 -128 -5 -125 -105 -125 l-83 0 -11 38 c-8 25 -10 161 -8 420 3 443 -4 412 104 412 91 0 101 -15 107 -160 l2 -65 99 -3 c64 -2 103 1 112 9 18 14 20 212 3 290 -14 66 -63 122 -126 143 -63 22 -349 21 -408 0z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M2542 1778 c-17 -17 -17 -1299 0 -1316 8 -8 48 -12 114 -12 84 0 103 3 108 16 3 9 6 100 6 202 1 184 1 188 28 240 l28 53 13 -38 c12 -33 38 -95 71 -168 5 -11 28 -65 50 -120 23 -55 51 -119 62 -142 l21 -43 112 0 c140 0 145 4 109 86 -55 125 -67 151 -94 209 -40 86 -70 153 -70 159 0 3 -25 58 -56 123 -30 65 -56 125 -57 134 0 9 26 72 60 140 220 449 217 443 214 477 -1 9 -31 12 -114 12 -98 0 -116 -3 -130 -18 -10 -10 -17 -21 -17 -25 0 -3 -52 -111 -115 -239 l-114 -233 -1 242 c0 133 -3 248 -6 257 -5 13 -24 16 -108 16 -66 0 -106 -4 -114 -12z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M3395 1778 c-3 -7 -4 -308 -3 -668 l3 -655 325 -3 c297 -2 326 -1 338 15 19 26 16 187 -4 207 -13 14 -48 16 -225 16 l-209 0 0 160 0 160 168 2 167 3 0 110 0 110 -167 3 -168 2 0 160 0 160 219 0 c190 0 220 2 225 16 11 29 7 189 -6 202 -18 18 -657 17 -663 0z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M4184 1777 c-2 -7 -3 -58 -2 -113 l3 -99 107 -3 108 -3 2 -552 3 -552 115 0 115 0 3 552 2 552 108 3 107 3 3 90 c2 50 0 100 -3 113 l-5 22 -331 0 c-264 0 -331 -3 -335 -13z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M5345 1774 c-50 -18 -101 -67 -117 -113 -10 -27 -13 -157 -13 -531 0 -272 3 -511 7 -530 11 -51 67 -108 127 -131 42 -16 77 -19 200 -19 208 0 268 24 316 125 24 52 25 62 23 192 l-3 138 -104 3 c-66 2 -108 -1 -117 -9 -9 -7 -14 -41 -16 -107 l-3 -97 -88 -3 c-86 -3 -87 -2 -97 23 -6 16 -10 183 -10 416 0 454 -6 429 101 429 94 0 99 -5 99 -106 0 -123 1 -124 118 -124 63 0 102 4 110 12 19 19 16 252 -5 308 -22 60 -66 106 -121 125 -62 21 -349 20 -407 -1z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M6047 1784 c-9 -9 -9 -1296 -1 -1318 9 -24 219 -24 228 0 3 9 6 135 6 280 l0 264 100 0 100 0 0 -264 c0 -145 3 -271 6 -280 9 -24 219 -24 228 0 8 20 8 1288 0 1308 -9 24 -219 24 -228 0 -3 -9 -6 -132 -6 -275 l0 -259 -100 0 -100 0 -2 273 -3 272 -111 3 c-60 1 -113 -1 -117 -4z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M7099 1763 c-6 -16 -26 -102 -45 -193 -18 -91 -41 -196 -50 -235 -20 -91 -34 -152 -79 -365 -20 -96 -46 -215 -56 -265 -45 -208 -49 -231 -44 -242 3 -9 35 -13 114 -13 94 0 110 2 116 17 6 18 32 146 42 210 l5 32 144 3 c79 2 144 3 144 3 0 0 9 -50 19 -112 28 -161 20 -153 152 -153 l111 0 -5 47 c-3 27 -12 82 -22 123 -9 41 -23 107 -31 145 -23 114 -69 329 -104 480 -11 50 -27 124 -35 165 -8 41 -21 104 -29 140 -8 36 -23 103 -33 150 l-18 85 -143 3 -142 3 -11 -28z m176 -463 c35 -182 58 -307 62 -342 l5 -38 -97 0 -97 0 6 28 c6 27 71 390 84 467 l6 40 7 -35 c4 -19 14 -73 24 -120z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M7645 1768 c-3 -13 -5 -63 -3 -113 l3 -90 108 -3 107 -3 0 -539 c0 -296 3 -545 6 -554 9 -24 219 -24 228 0 3 9 6 258 6 554 l0 539 108 3 107 3 0 110 0 110 -332 3 -333 2 -5 -22z" />
<path
android:fillColor="#04436A"
android:strokeWidth="1"
android:pathData="M4842 674 c-20 -14 -22 -23 -22 -102 1 -119 3 -122 122 -122 60 0 98 4 106 12 8 8 12 47 12 110 0 86 -2 98 -19 108 -32 16 -173 13 -199 -6z" />
</group>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFFD100"
android:fillType="evenOdd"
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M12,12m-11,0a11,11 0,1 1,22 0a11,11 0,1 1,-22 0"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFF2A57"
android:fillType="evenOdd"
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M12,12m-11,0a11,11 0,1 1,22 0a11,11 0,1 1,-22 0"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFCBCED1"
android:fillType="evenOdd"
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M12,12m-11,0a11,11 0,1 1,22 0a11,11 0,1 1,-22 0"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FF2DE0A5"
android:fillType="evenOdd"
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M12,12m-11,0a11,11 0,1 1,22 0a11,11 0,1 1,-22 0"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/image_app_name"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:src="@drawable/ic_app_name"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:layout_width="160dp"
android:layout_height="160dp"
android:src="@drawable/ic_launcher_foreground"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/image_app_name"
android:adjustViewBounds="true"
android:scaleX="1.5"
android:scaleY="1.5"
/>
<TextView
android:id="@+id/text_version_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Version alpha2.0.1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_app_name"
android:layout_marginTop="16dp"
android:textColor="@color/colorSecondaryText"/>
<TextView
android:id="@+id/text_build_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Build # 2000"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_version_name"
android:layout_marginTop="8dp"
android:textColor="@color/colorSecondaryText"/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:theme="@style/AppTheme"
tools:context="chat.rocket.android.settings.about.ui.AboutActivity">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar_password" />
<include layout="@layout/about_view"/>
</LinearLayout>
\ No newline at end of file
...@@ -28,21 +28,23 @@ ...@@ -28,21 +28,23 @@
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">
<android.support.design.widget.NavigationView <android.support.design.widget.NavigationView
android:id="@+id/view_navigation" android:id="@+id/view_navigation"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
app:menu="@menu/navigation" app:headerLayout="@layout/nav_header"
app:headerLayout="@layout/nav_header" /> app:menu="@menu/navigation" />
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/accounts_list" android:id="@+id/accounts_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="@dimen/nav_header_height" android:layout_marginTop="@dimen/nav_header_height"
android:elevation="20dp"
android:background="@color/white"
android:alpha="0" android:alpha="0"
android:visibility="gone"/> android:background="@color/white"
android:elevation="20dp"
android:visibility="gone" />
</FrameLayout> </FrameLayout>
</android.support.v4.widget.DrawerLayout> </android.support.v4.widget.DrawerLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackground" > android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:background="?selectableItemBackground">
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/server_logo" android:id="@+id/server_logo"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="8dp" app:actualImageScaleType="centerInside"
android:layout_marginStart="16dp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:actualImageScaleType="centerInside" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/text_server_url" android:id="@+id/text_server_url"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="16dp" android:ellipsize="end"
android:maxLines="1"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintTop_toTopOf="@id/server_logo"
app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintTop_toTopOf="@id/server_logo"
tools:text="https://open.rocket.chat" /> tools:text="https://open.rocket.chat" />
<TextView <TextView
...@@ -36,10 +36,11 @@ ...@@ -36,10 +36,11 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="16dp" android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="@id/server_logo" app:layout_constraintBottom_toBottomOf="@id/server_logo"
app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
tools:text="Lucio Maciel"/> app:layout_constraintStart_toEndOf="@id/server_logo"
tools:text="Lucio Maciel" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackground" > android:padding="16dp"
android:background="?selectableItemBackground">
<ImageView <ImageView
android:id="@+id/server_logo" android:id="@+id/server_logo"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_add_24dp" android:src="@drawable/ic_add_24dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
...@@ -23,8 +19,7 @@ ...@@ -23,8 +19,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="16dp" android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:text="@string/action_add_account" android:text="@string/action_add_account"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="?android:dividerHorizontal"
android:orientation="vertical"
android:showDividers="end">
<TextView
android:id="@+id/text_online"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_online_24dp"
android:text="@string/action_online" />
<TextView
android:id="@+id/text_away"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_away_24dp"
android:text="@string/action_away" />
<TextView
android:id="@+id/text_busy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_busy_24dp"
android:text="@string/action_busy" />
<TextView
android:id="@+id/text_invisible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_invisible_24dp"
android:text="@string/action_invisible" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -11,11 +10,11 @@ ...@@ -11,11 +10,11 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:foreground="@drawable/black_gradient" android:foreground="@drawable/black_gradient"
app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:actualImageScaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" /> tools:src="@tools:sample/backgrounds/scenic" />
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
...@@ -33,50 +32,57 @@ ...@@ -33,50 +32,57 @@
android:id="@+id/account_container" android:id="@+id/account_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:padding="4dp"
android:elevation="2dp"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent" android:elevation="2dp"
app:layout_constraintStart_toStartOf="@+id/image_avatar" android:paddingBottom="4dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="4dp"
app:layout_constraintTop_toBottomOf="@+id/image_avatar"> app:layout_constraintTop_toBottomOf="@+id/image_avatar">
<ImageView
android:id="@+id/image_user_status"
android:layout_width="14dp"
android:layout_height="14dp"
app:layout_constraintStart_toStartOf="parent" />
<TextView <TextView
android:id="@+id/text_name" android:id="@+id/text_user_name"
style="@style/Sender.Name.TextView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginEnd="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:layout_marginStart="10dp"
android:textColor="@color/white" android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@+id/account_expand" app:layout_constraintBottom_toBottomOf="@+id/image_user_status"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/image_account_expand"
app:layout_constraintStart_toEndOf="@+id/image_user_status"
app:layout_constraintTop_toTopOf="@+id/image_user_status"
tools:text="Lucio Maciel" /> tools:text="Lucio Maciel" />
<TextView <TextView
android:id="@+id/text_server" android:id="@+id/text_server_url"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginEnd="10dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/white" android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@+id/account_expand" app:layout_constraintEnd_toStartOf="@+id/image_account_expand"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_name" app:layout_constraintTop_toBottomOf="@+id/text_user_name"
tools:text="https://open.rocket.chat" /> tools:text="https://open.rocket.chat" />
<ImageView <ImageView
android:id="@+id/account_expand" android:id="@+id/image_account_expand"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_expand_more_24dp" android:src="@drawable/ic_expand_more_24dp"
android:tint="@color/whitesmoke" android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="@+id/text_server_url"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
\ No newline at end of file
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
<string name="title_settings">सेटिंग्स</string> <string name="title_settings">सेटिंग्स</string>
<string name="title_password">पासवर्ड बदलें</string> <string name="title_password">पासवर्ड बदलें</string>
<string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string> <string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string>
<string name="title_about">परिचय</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">जुडिये</string> <string name="action_connect">जुडिये</string>
...@@ -28,10 +29,15 @@ ...@@ -28,10 +29,15 @@
<string name="action_confirm_password">पासवर्ड परिवर्तन की पुष्टि करें</string> <string name="action_confirm_password">पासवर्ड परिवर्तन की पुष्टि करें</string>
<string name="action_join_chat">चैट में शामिल हों</string> <string name="action_join_chat">चैट में शामिल हों</string>
<string name="action_add_account">खाता जोड़ो</string> <string name="action_add_account">खाता जोड़ो</string>
<string name="action_online">ऑनलाइन</string>
<string name="action_away">दूर</string>
<string name="action_busy">व्यस्त</string>
<string name="action_invisible">अदृश्य</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_password">पासवर्ड बदलें</item> <item name="item_password">पासवर्ड बदलें</item>
<item name="item_password">परिचय</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -82,6 +88,8 @@ ...@@ -82,6 +88,8 @@
<string name="msg_ver_not_minimum"> <string name="msg_ver_not_minimum">
ऐसा लगता है कि आपका सर्वर संस्करण न्यूनतम आवश्यक संस्करण %1$s से कम है।\nकृपया लॉगिन करने के लिए अपने सर्वर को अपग्रेड करें! ऐसा लगता है कि आपका सर्वर संस्करण न्यूनतम आवश्यक संस्करण %1$s से कम है।\nकृपया लॉगिन करने के लिए अपने सर्वर को अपग्रेड करें!
</string> </string>
<string name="msg_version">वर्शन</string>
<string name="msg_build">बिल्ड</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string> <string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string>
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
<string name="title_settings">Configurações</string> <string name="title_settings">Configurações</string>
<string name="title_password">Alterar senha</string> <string name="title_password">Alterar senha</string>
<string name="title_update_profile">Editar perfil</string> <string name="title_update_profile">Editar perfil</string>
<string name="title_about">Sobre</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Conectar</string> <string name="action_connect">Conectar</string>
...@@ -27,11 +28,16 @@ ...@@ -27,11 +28,16 @@
<string name="action_files">Arquivos</string> <string name="action_files">Arquivos</string>
<string name="action_confirm_password">Confirme a nova senha</string> <string name="action_confirm_password">Confirme a nova senha</string>
<string name="action_join_chat">Entrar no Chat</string> <string name="action_join_chat">Entrar no Chat</string>
<string name="action_add_account">Adicionar Conta</string> <string name="action_add_account">Adicionar conta</string>
<string name="action_online">Online</string>
<string name="action_away">Ausente</string>
<string name="action_busy">Ocupado</string>
<string name="action_invisible">Invisível</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_password">Alterar senha</item> <item name="item_password">Alterar senha</item>
<item name="item_password">Sobre</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -50,7 +56,7 @@ ...@@ -50,7 +56,7 @@
<string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string> <string name="msg_new_user_agreement">Ao proceder você concorda com nossos %1$s e %2$s</string>
<string name="msg_2fa_code">Código 2FA</string> <string name="msg_2fa_code">Código 2FA</string>
<string name="msg_yesterday">ontem</string> <string name="msg_yesterday">ontem</string>
<string name="msg_message">Messagem</string> <string name="msg_message">Mensagem</string>
<string name="msg_this_room_is_read_only">Este chat é apenas de leitura</string> <string name="msg_this_room_is_read_only">Este chat é apenas de leitura</string>
<string name="msg_invalid_2fa_code">Código 2FA inválido</string> <string name="msg_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_invalid_file">Arquivo inválido</string> <string name="msg_invalid_file">Arquivo inválido</string>
...@@ -75,6 +81,8 @@ ...@@ -75,6 +81,8 @@
<string name="msg_preview_audio">Audio</string> <string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Foto</string> <string name="msg_preview_photo">Foto</string>
<string name="msg_no_messages_yet">Nenhuma mensagem ainda</string> <string name="msg_no_messages_yet">Nenhuma mensagem ainda</string>
<string name="msg_version">Versão</string>
<string name="msg_build">Build</string>
<string name="msg_ok">OK</string> <string name="msg_ok">OK</string>
<string name="msg_ver_not_recommended"> <string name="msg_ver_not_recommended">
Parece que a versão do seu servidor está abaixo da recomendada %1$s.\nVocê ainda assim pode logar e continuar mas podem ocorrer alguns problemas inesperados. Parece que a versão do seu servidor está abaixo da recomendada %1$s.\nVocê ainda assim pode logar e continuar mas podem ocorrer alguns problemas inesperados.
......
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
<dimen name="message_time_text_size">12sp</dimen> <dimen name="message_time_text_size">12sp</dimen>
<dimen name="nav_header_height">140dp</dimen>
<!-- Emoji --> <!-- Emoji -->
<dimen name="picker_padding_bottom">16dp</dimen> <dimen name="picker_padding_bottom">16dp</dimen>
<dimen name="supposed_keyboard_height">252dp</dimen> <dimen name="supposed_keyboard_height">252dp</dimen>
...@@ -35,6 +37,5 @@ ...@@ -35,6 +37,5 @@
<!-- 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
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<string name="title_settings">Settings</string> <string name="title_settings">Settings</string>
<string name="title_password">Change Password</string> <string name="title_password">Change Password</string>
<string name="title_update_profile">Update profile</string> <string name="title_update_profile">Update profile</string>
<string name="title_about">About</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Connect</string> <string name="action_connect">Connect</string>
...@@ -28,11 +29,16 @@ ...@@ -28,11 +29,16 @@
<string name="action_files">Files</string> <string name="action_files">Files</string>
<string name="action_confirm_password">Confirm Password Change</string> <string name="action_confirm_password">Confirm Password Change</string>
<string name="action_join_chat">Join Chat</string> <string name="action_join_chat">Join Chat</string>
<string name="action_add_account">Add Account</string> <string name="action_add_account">Add account</string>
<string name="action_online">Online</string>
<string name="action_away">Away</string>
<string name="action_busy">Busy</string>
<string name="action_invisible">Invisible</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_password">Change Password</item> <item name="item_password">Change Password</item>
<item name="item_password">About</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -77,6 +83,8 @@ ...@@ -77,6 +83,8 @@
<string name="msg_preview_audio">Audio</string> <string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Photo</string> <string name="msg_preview_photo">Photo</string>
<string name="msg_no_messages_yet">No messages yet</string> <string name="msg_no_messages_yet">No messages yet</string>
<string name="msg_version">Version</string>
<string name="msg_build">Build</string>
<string name="msg_ok">OK</string> <string name="msg_ok">OK</string>
<string name="msg_ver_not_recommended"> <string name="msg_ver_not_recommended">
Looks like your server version is below the recommended version %1$s.\nYou can still login but you may experience unexpected behaviors.</string> Looks like your server version is below the recommended version %1$s.\nYou can still login but you may experience unexpected behaviors.</string>
......
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