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 {
applicationId "chat.rocket.android"
minSdkVersion 21
targetSdkVersion versions.targetSdk
versionCode 2003
versionName "2.0.0-beta3"
versionCode 2006
versionName "2.0.0-beta4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
......
......@@ -20,7 +20,6 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<activity
android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation"
......@@ -34,41 +33,33 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".server.ui.ChangeServerActivity"
android:theme="@style/AuthenticationTheme" />
<activity
android:name=".main.ui.MainActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".webview.ui.WebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".webview.cas.ui.CasWebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".webview.oauth.ui.OauthWebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".chatroom.ui.ChatRoomActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".chatroom.ui.PinnedMessagesActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".settings.password.ui.PasswordActivity"
android:theme="@style/AppTheme" />
......@@ -92,7 +83,6 @@
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service
android:name=".push.GcmListenerService"
android:exported="false">
......@@ -104,6 +94,9 @@
<meta-data
android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
<activity android:name=".settings.about.ui.AboutActivity"
android:theme="@style/AppTheme"/>
</application>
</manifest>
\ No newline at end of file
......@@ -106,15 +106,45 @@ object DrawableHelper {
* @return The user status drawable.
*/
fun getUserStatusDrawable(userStatus: UserStatus, context: Context): Drawable {
val userStatusDrawable = getDrawableFromId(R.drawable.ic_user_status_black, context).mutate()
wrapDrawable(userStatusDrawable)
when (userStatus) {
is UserStatus.Online -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusOnline)
is UserStatus.Busy -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusBusy)
is UserStatus.Away -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusAway)
is UserStatus.Offline -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusOffline)
else -> tintDrawable(userStatusDrawable, context, R.color.colorUserStatusOffline)
return when (userStatus) {
is UserStatus.Online -> {
getDrawableFromId(R.drawable.ic_status_online_24dp, context)
}
is UserStatus.Away -> {
getDrawableFromId(R.drawable.ic_status_away_24dp, context)
}
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
import chat.rocket.android.authentication.domain.model.toToken
import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.helper.CrashlyticsTree
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
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.common.model.Token
import chat.rocket.core.model.Value
......@@ -148,12 +149,12 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
private fun migrateServerInfo(url: String, authToken: String, settings: PublicSettings, user: RealmUser) {
val userId = user._id
val avatar = UrlHelper.getAvatarUrl(url, user.username!!)
val avatar = url.avatarUrl(user.username!!)
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(url, it)
url.serverLogoUrl(it)
}
val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(url, it)
url.serverLogoUrl(it)
}
val account = Account(url, icon, logo, user.username!!, avatar)
launch(CommonPool) {
......
package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.BuildConfig
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.model.Token
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.*
import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.delay
import timber.log.Timber
import java.util.concurrent.TimeUnit
......@@ -103,7 +100,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private fun setupCasView() {
if (settings.isCasAuthenticationEnabled()) {
val token = generateRandomString(17)
view.setupCasButtonListener(UrlHelper.getCasUrl(settings.casLoginUrl(), currentServer, token), token)
view.setupCasButtonListener(settings.casLoginUrl().casUrl(currentServer, token), token)
view.showCasButton()
}
}
......@@ -118,7 +115,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private fun setupOauthServicesView() {
launchUI(strategy) {
try {
val services = client.settingsOauth().services
val services = retryIO("settingsOauth()") {
client.settingsOauth().services
}
if (services.isNotEmpty()) {
val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
var totalSocialAccountsEnabled = 0
......@@ -195,30 +194,32 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
view.disableUserInput()
view.showLoading()
try {
val token = when (loginType) {
TYPE_LOGIN_USER_EMAIL -> {
if (usernameOrEmail.isEmail()) {
client.loginWithEmail(usernameOrEmail, password)
} else {
if (settings.isLdapAuthenticationEnabled()) {
client.loginWithLdap(usernameOrEmail, password)
val token = retryIO("login") {
when (loginType) {
TYPE_LOGIN_USER_EMAIL -> {
if (usernameOrEmail.isEmail()) {
client.loginWithEmail(usernameOrEmail, password)
} else {
client.login(usernameOrEmail, password)
if (settings.isLdapAuthenticationEnabled()) {
client.loginWithLdap(usernameOrEmail, password)
} else {
client.login(usernameOrEmail, password)
}
}
}
}
TYPE_LOGIN_CAS -> {
delay(3, TimeUnit.SECONDS)
client.loginWithCas(credentialToken)
}
TYPE_LOGIN_OAUTH -> {
client.loginWithOauth(credentialToken, credentialSecret)
}
else -> {
throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS or TYPE_LOGIN_OAUTH")
TYPE_LOGIN_CAS -> {
delay(3, TimeUnit.SECONDS)
client.loginWithCas(credentialToken)
}
TYPE_LOGIN_OAUTH -> {
client.loginWithOauth(credentialToken, credentialSecret)
}
else -> {
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) {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
saveAccount(username)
......@@ -258,12 +259,12 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
currentServer.serverLogoUrl(it)
}
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)
saveAccountInteractor.save(account)
}
......
......@@ -3,13 +3,15 @@ package chat.rocket.android.authentication.registerusername.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.launchUI
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.model.Token
import chat.rocket.common.util.ifNull
......@@ -41,7 +43,9 @@ class RegisterUsernamePresenter @Inject constructor(
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
try {
val me = client.updateOwnBasicInformation(username = username)
val me = retryIO("updateOwnBasicInformation(username = $username)") {
client.updateOwnBasicInformation(username = username)
}
val registeredUsername = me.username
if (registeredUsername != null) {
saveAccount(registeredUsername)
......@@ -75,12 +79,12 @@ class RegisterUsernamePresenter @Inject constructor(
private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
currentServer.serverLogoUrl(it)
}
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)
saveAccountInteractor.save(account)
}
......
......@@ -3,10 +3,10 @@ package chat.rocket.android.authentication.server.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.util.extensions.isValidUrl
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.util.ifNull
import javax.inject.Inject
......@@ -18,7 +18,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor) {
fun connect(server: String) {
if (!UrlHelper.isValidUrl(server)) {
if (!server.isValidUrl()) {
view.showInvalidServerUrlMessage()
} else {
launchUI(strategy) {
......
......@@ -3,20 +3,22 @@ package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper
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.model.Account
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.privacyPolicyUrl
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.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.internal.rest.signup
import chat.rocket.core.model.Myself
import javax.inject.Inject
......@@ -60,10 +62,10 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
try {
// 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?
client.login(username, password)
val me = client.me()
retryIO("login") { client.login(username, password) }
val me = retryIO("me") { client.me() }
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me)
registerPushToken()
......@@ -88,13 +90,13 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
fun termsOfService() {
serverInteractor.get()?.let {
navigator.toWebPage(UrlHelper.getTermsOfServiceUrl(it))
navigator.toWebPage(it.termsOfServiceUrl())
}
}
fun privacyPolicy() {
serverInteractor.get()?.let {
navigator.toWebPage(UrlHelper.getPrivacyPolicyUrl(it))
navigator.toWebPage(it.privacyPolicyUrl())
}
}
......@@ -108,12 +110,12 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
currentServer.serverLogoUrl(it)
}
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)
saveAccountInteractor.save(account)
}
......
......@@ -3,13 +3,15 @@ package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.launchUI
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.RocketChatException
import chat.rocket.common.util.ifNull
......@@ -50,9 +52,10 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
view.showLoading()
try {
// The token is saved via the client TokenProvider
val token =
val token = retryIO("login") {
client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
val me = client.me()
}
val me = retryIO("me") { client.me() }
saveAccount(me)
tokenRepository.save(server, token)
registerPushToken()
......@@ -90,12 +93,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
currentServer.serverLogoUrl(it)
}
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)
saveAccountInteractor.save(account)
}
......
......@@ -92,8 +92,10 @@ interface ChatRoomView : LoadingView, MessageView {
/**
* Enables the send message button.
*
* @param sendFailed Whether the sent message has failed.
*/
fun enableSendMessageButton()
fun enableSendMessageButton(sendFailed: Boolean)
/**
* Clears the message composition.
......
......@@ -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_composer.*
import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
import kotlin.math.absoluteValue
......@@ -126,7 +125,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
activity?.apply {
(this as? ChatRoomActivity)?.showRoomTypeIcon(true)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
......@@ -135,6 +133,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun onDestroyView() {
recycler_view.removeOnScrollListener(endlessRecyclerViewScrollListener)
recycler_view.removeOnScrollListener(onScrollListener)
recycler_view.removeOnLayoutChangeListener(layoutChangeListener)
presenter.unsubscribeMessages(chatRoomId)
handler.removeCallbacksAndMessages(null)
unsubscribeTextMessage()
......@@ -203,58 +205,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, presenter,
reactionListener = this@ChatRoomFragment)
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) {
recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
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.addOnScrollListener(endlessRecyclerViewScrollListener)
}
recycler_view.addOnScrollListener(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)
}
}
})
recycler_view.addOnLayoutChangeListener(layoutChangeListener)
recycler_view.addOnScrollListener(onScrollListener)
}
val oldMessagesCount = adapter.itemCount
......@@ -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) {
if (!text.isBlank()) {
if (!text.startsWith("/")) {
......@@ -296,10 +306,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_send.isEnabled = false
}
override fun enableSendMessageButton() {
override fun enableSendMessageButton(sendFailed: Boolean) {
button_send.isEnabled = true
text_message.isEnabled = true
text_message.erase()
if (!sendFailed) {
clearMessageComposition()
}
}
override fun clearMessageComposition() {
......@@ -463,18 +475,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
private fun setupRecyclerView() {
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
Timber.i("Scrolling vertically: $dy")
if (!recyclerView.canScrollVertically(1)) {
button_fab.hide()
} else {
if (dy < 0 && !button_fab.isVisible()) {
button_fab.show()
}
}
// Initialize the endlessRecyclerViewScrollListener so we don't NPE at onDestroyView
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
linearLayoutManager.stackFromEnd = true
recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
endlessRecyclerViewScrollListener = object :
EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
presenter.loadMessages(chatRoomId, chatRoomType, page * 30L)
}
})
}
recycler_view.addOnScrollListener(fabScrollListener)
}
private fun setupFab() {
......@@ -522,8 +534,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
var textMessage = citation ?: ""
textMessage += text_message.textContent
sendMessage(textMessage)
clearMessageComposition()
}
button_show_attachment_options.setOnClickListener {
......@@ -588,7 +598,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
text_message.requestFocus()
emojiKeyboardPopup.showAtBottomPending()
KeyboardHelper.showSoftKeyboard(text_message)
}
setReactionButtonIcon(R.drawable.ic_keyboard_black_24dp)
} else {
......
......@@ -9,9 +9,9 @@ import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import chat.rocket.android.R
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType
......@@ -235,7 +235,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val username = message.sender?.username ?: "?"
return baseUrl?.let {
UrlHelper.getAvatarUrl(baseUrl, username)
baseUrl.avatarUrl(username)
}
}
......
......@@ -12,6 +12,7 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.chatRooms
import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType
......@@ -94,7 +95,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
try {
val roomList = getChatRoomsInteractor.getByName(currentServer, name)
if (roomList.isEmpty()) {
val (users, rooms) = client.spotlight(name)
val (users, rooms) = retryIO("spotlight($name)") {
client.spotlight(name)
}
val chatRoomsCombined = mutableListOf<ChatRoom>()
chatRoomsCombined.addAll(usersToChatRooms(users))
chatRoomsCombined.addAll(roomsToChatRooms(rooms))
......@@ -161,7 +164,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
private suspend fun loadRooms(): List<ChatRoom> {
val chatRooms = manager.chatRooms().update
val chatRooms = retryIO("chatRooms") { manager.chatRooms().update }
val sortedRooms = sortRooms(chatRooms)
Timber.d("Loaded rooms: ${sortedRooms.size}")
saveChatRoomsInteractor.save(currentServer, sortedRooms)
......
......@@ -12,11 +12,11 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.checkIfMyself
import chat.rocket.android.server.domain.PublicSettings
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.inflate
import chat.rocket.android.util.extensions.setVisible
......@@ -75,7 +75,7 @@ class ChatRoomsAdapter(private val context: Context,
private fun bindAvatar(chatRoom: ChatRoom, drawee: SimpleDraweeView) {
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) {
......
......@@ -158,15 +158,16 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
listJob?.cancel()
listJob = launch(UI) {
val adapter = recycler_view.adapter as SimpleSectionedRecyclerViewAdapter
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android.dev/issues/5a90d4718cb3c2fa63b3f557?time=last-seven-days
// TODO - fix this bug to reenable DiffUtil
val diff = async(CommonPool) {
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android/issues/5ac2916c36c7b235275ccccf
// TODO - fix this bug to re-enable DiffUtil
/*val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await()
}.await()*/
if (isActive) {
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
setSections()
......
package chat.rocket.android.helper
import chat.rocket.android.util.extensions.removeTrailingSlash
object OauthHelper {
/**
......@@ -27,7 +29,7 @@ object OauthHelper {
fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://accounts.google.com/o/oauth2/v2/auth" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/google?close" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/google?close" +
"&state=$state" +
"&response_type=code" +
"&scope=email%20profile"
......@@ -44,7 +46,7 @@ object OauthHelper {
fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://linkedin.com/oauth/v2/authorization" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/linkedin?close" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/linkedin?close" +
"&state=$state" +
"&response_type=code"
}
......@@ -60,7 +62,7 @@ object OauthHelper {
fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://gitlab.com/oauth/authorize" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/gitlab?close" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/gitlab?close" +
"&state=$state" +
"&response_type=code" +
"&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
import kotlinx.android.synthetic.main.item_account.view.*
class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(account: Account) {
with(itemView) {
server_logo.setImageURI(account.serverLogo)
......
......@@ -5,50 +5,64 @@ import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account
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(
private val accounts: List<Account>,
private val selector: AccountSelector
private val selector: Selector
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
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))
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) =
if (position == accounts.size) VIEW_TYPE_ADD_ACCOUNT else VIEW_TYPE_ACCOUNT
override fun getItemViewType(position: Int): Int {
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) {
when (holder) {
is StatusViewHolder -> bindStatusViewHolder(holder)
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) {
val account = accounts[position]
val account = accounts[position - 1]
holder.bind(account)
holder.itemView.setOnClickListener {
selector.onAccountSelected(account.serverUrl)
}
}
private fun bindAddAccountViewHolder(holder: AddAccountViewHolder, position: Int) {
private fun bindAddAccountViewHolder(holder: AddAccountViewHolder) {
holder.itemView.setOnClickListener {
selector.onAddedAccountSelected()
}
}
}
interface AccountSelector {
interface Selector {
fun onStatusSelected(userStatus: UserStatus)
fun onAccountSelected(serverUrl: String)
fun onAddedAccountSelected()
}
private const val VIEW_TYPE_ACCOUNT = 0
private const val VIEW_TYPE_ADD_ACCOUNT = 1
\ No newline at end of file
}
\ 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
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.main.viewmodel.NavHeaderViewModelMapper
......@@ -12,10 +11,14 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extensions.launchUI
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.RocketChatException
import chat.rocket.common.util.ifNull
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.me
import chat.rocket.core.internal.rest.unregisterPushToken
......@@ -52,9 +55,8 @@ class MainPresenter @Inject constructor(
checkServerInfo()
launchUI(strategy) {
try {
val me = client.me()
val me = retryIO("me") { client.me() }
val model = navHeaderMapper.mapToViewModel(me)
saveAccount(model)
view.setupNavHeader(model, getAccountsInteractor.get())
} catch (ex: Exception) {
......@@ -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 {
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)
}
......@@ -90,7 +98,7 @@ class MainPresenter @Inject constructor(
launchUI(strategy) {
try {
clearTokens()
client.logout()
retryIO("logout") { client.logout() }
} catch (exception: RocketChatException) {
Timber.d(exception, "Error calling logout")
exception.message?.let {
......@@ -117,7 +125,11 @@ class MainPresenter @Inject constructor(
serverInteractor.clear()
val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN)
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)
}
......@@ -142,6 +154,21 @@ class MainPresenter @Inject constructor(
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?) {
token?.let {
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, token)
......
......@@ -4,8 +4,24 @@ import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.model.Account
import chat.rocket.core.internal.realtime.UserStatus
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()
}
\ No newline at end of file
......@@ -11,7 +11,7 @@ import android.view.MenuItem
import android.view.View
import chat.rocket.android.BuildConfig
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.presentation.MainPresenter
import chat.rocket.android.main.presentation.MainView
......@@ -21,6 +21,7 @@ import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast
import chat.rocket.core.internal.realtime.UserStatus
import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID
import dagger.android.AndroidInjection
......@@ -42,6 +43,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
@Inject lateinit var presenter: MainPresenter
private var isFragmentAdded: Boolean = false
private var expanded = false
private val headerLayout by lazy { view_navigation.getHeaderView(0) }
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
......@@ -79,14 +81,32 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
}
}
override fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>) {
Timber.d("Setting up nav header: $model")
val headerLayout = view_navigation.getHeaderView(0)
headerLayout.text_name.text = model.username
headerLayout.text_server.text = model.server
headerLayout.image_avatar.setImageURI(model.avatar)
headerLayout.server_logo.setImageURI(model.serverLogo)
setupAccountsList(headerLayout, accounts)
override fun showUserStatus(userStatus: UserStatus) {
headerLayout.apply {
image_user_status.setImageDrawable(
DrawableHelper.getUserStatusDrawable(
userStatus,
this.context
)
)
}
}
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() {
......@@ -112,7 +132,11 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
private fun setupAccountsList(header: View, accounts: List<Account>) {
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) {
presenter.changeServer(serverUrl)
}
......@@ -120,11 +144,10 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
override fun onAddedAccountSelected() {
presenter.addNewServer()
}
})
header.account_container.setOnClickListener {
header.account_expand.rotateBy(180f)
header.image_account_expand.rotateBy(180f)
if (expanded) {
accounts_list.fadeOut()
} else {
......
package chat.rocket.android.main.viewmodel
import chat.rocket.common.model.UserStatus
data class NavHeaderViewModel(
val username: String,
val server: String,
val avatar: String?,
val userDisplayName: String,
val userStatus: UserStatus?,
val userAvatar: String?,
val serverUrl: String,
val serverLogo: String?
)
\ No newline at end of file
package chat.rocket.android.main.viewmodel
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.*
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.core.model.Myself
import javax.inject.Inject
class NavHeaderViewModelMapper @Inject constructor(serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor) {
class NavHeaderViewModelMapper @Inject constructor(
serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor
) {
private val currentServer = serverInteractor.get()!!
private var settings: PublicSettings = getSettingsInteractor.get(currentServer)
fun mapToViewModel(me: Myself): NavHeaderViewModel {
val username = mapUsername(me)
val thumb = me.username?.let { UrlHelper.getAvatarUrl(currentServer, it) }
val displayName = mapDisplayName(me)
val status = me.status
val avatar = me.username?.let { currentServer.avatarUrl(it) }
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 realName = me.name
val senderName = if (settings.useRealName()) realName else username
......
......@@ -6,6 +6,7 @@ import chat.rocket.android.members.viewmodel.MemberViewModelMapper
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......@@ -26,7 +27,9 @@ class MembersPresenter @Inject constructor(private val view: MembersView,
try {
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)
view.showMembers(memberViewModels, members.total)
} catch (ex: RocketChatException) {
......
package chat.rocket.android.members.viewmodel
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.common.model.User
import chat.rocket.core.model.Value
......@@ -25,7 +25,7 @@ class MemberViewModel(private val member: User, private val settings: Map<String
private fun getUserAvatar(): String? {
val username = member.username ?: "?"
return baseUrl?.let {
UrlHelper.getAvatarUrl(baseUrl, username, "png")
baseUrl.avatarUrl(username,"png")
}
}
......
package chat.rocket.android.profile.presentation
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.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
......@@ -25,9 +26,9 @@ class ProfilePresenter @Inject constructor(private val view: ProfileView,
launchUI(strategy) {
view.showLoading()
try {
val myself = client.me()
val myself = retryIO("me") { client.me() }
myselfId = myself.id
val avatarUrl = UrlHelper.getAvatarUrl(serverUrl, myself.username!!)
val avatarUrl = serverUrl.avatarUrl(myself.username!!)
view.showProfile(
avatarUrl,
myself.name ?: "",
......@@ -51,9 +52,9 @@ class ProfilePresenter @Inject constructor(private val view: ProfileView,
view.showLoading()
try {
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()
loadUserProfile()
} catch (exception: RocketChatException) {
......
......@@ -5,7 +5,6 @@ import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.common.RocketChatException
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.registerPushToken
import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID
......
......@@ -5,6 +5,7 @@ import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.serverInfo
import timber.log.Timber
......@@ -14,21 +15,25 @@ abstract class CheckServerPresenter constructor(private val strategy: CancelStra
private val view: VersionCheckView) {
internal fun checkServerInfo() {
launchUI(strategy) {
val serverInfo = client.serverInfo()
val thisServerVersion = serverInfo.version
val isRequiredVersion = isRequiredServerVersion(thisServerVersion)
val isRecommendedVersion = isRecommendedServerVersion(thisServerVersion)
if (isRequiredVersion) {
if (isRecommendedVersion) {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: $thisServerVersion)")
try {
val serverInfo = retryIO(description = "serverInfo", times = 5) { client.serverInfo() }
val thisServerVersion = serverInfo.version
val isRequiredVersion = isRequiredServerVersion(thisServerVersion)
val isRecommendedVersion = isRecommendedServerVersion(thisServerVersion)
if (isRequiredVersion) {
if (isRecommendedVersion) {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: $thisServerVersion)")
} else {
view.alertNotRecommendedVersion()
}
} else {
view.alertNotRecommendedVersion()
}
} else {
if (!isRecommendedVersion) {
view.blockAndAlertNotRequiredVersion()
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
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.me
......@@ -22,7 +23,10 @@ class PasswordPresenter @Inject constructor (private val view: PasswordView,
try {
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.hideLoading()
......
......@@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.widget.AdapterView
import chat.rocket.android.R
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.presentation.SettingsView
import chat.rocket.android.util.extensions.inflate
......@@ -33,8 +34,10 @@ class SettingsFragment: Fragment(), SettingsView, AdapterView.OnItemClickListene
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when (parent?.getItemAtPosition(position).toString()) {
"Change Password" -> {
resources.getString(R.string.title_password) -> {
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
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.registerPushToken
import kotlinx.coroutines.experimental.CommonPool
......@@ -16,7 +17,9 @@ suspend fun RocketChatClient.registerPushToken(
launch(CommonPool) {
accounts.forEach { account ->
try {
factory.create(account.serverUrl).registerPushToken(token)
retryIO(description = "register push token: ${account.serverUrl}") {
factory.create(account.serverUrl).registerPushToken(token)
}
} catch (ex: Exception) {
Timber.d(ex, "Error registering Push token for ${account.serverUrl}")
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? {
fun Uri.getFileSize(context: Context): Int {
val cursor = context.contentResolver.query(this, null, null, null, null, null)
var fileSize: String? = null
cursor.use { cursor ->
val fileSize = cursor?.use {
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
if (cursor != null && cursor.moveToFirst()) {
if (cursor.moveToFirst()) {
if (!cursor.isNull(sizeIndex)) {
fileSize = cursor.getString(sizeIndex)
return@use cursor.getString(sizeIndex)
}
}
return@use null
}
return fileSize?.toIntOrNull() ?: -1
}
......
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="#00000000"
android:centerColor="#30000000"
android:endColor="#00000000"
android:startColor="#C0000000"
android:type="linear" />
</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 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start">
<android.support.design.widget.NavigationView
android:id="@+id/view_navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:menu="@menu/navigation"
app:headerLayout="@layout/nav_header" />
app:headerLayout="@layout/nav_header"
app:menu="@menu/navigation" />
<android.support.v7.widget.RecyclerView
android:id="@+id/accounts_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/nav_header_height"
android:elevation="20dp"
android:background="@color/white"
android:alpha="0"
android:visibility="gone"/>
android:background="@color/white"
android:elevation="20dp"
android:visibility="gone" />
</FrameLayout>
</android.support.v4.widget.DrawerLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground" >
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:background="?selectableItemBackground">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/server_logo"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:actualImageScaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:actualImageScaleType="centerInside" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_server_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="@id/server_logo"
app:layout_constraintStart_toEndOf="@id/server_logo"
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" />
<TextView
......@@ -36,10 +36,11 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="@id/server_logo"
app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintEnd_toEndOf="parent"
tools:text="Lucio Maciel"/>
app:layout_constraintStart_toEndOf="@id/server_logo"
tools:text="Lucio Maciel" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground" >
android:padding="16dp"
android:background="?selectableItemBackground">
<ImageView
android:id="@+id/server_logo"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_add_24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
......@@ -23,8 +19,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:text="@string/action_add_account"
app:layout_constraintTop_toTopOf="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"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
......@@ -11,11 +10,11 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:foreground="@drawable/black_gradient"
app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:actualImageScaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" />
<com.facebook.drawee.view.SimpleDraweeView
......@@ -33,50 +32,57 @@
android:id="@+id/account_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginTop="16dp"
android:padding="4dp"
android:elevation="2dp"
android:background="?selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/image_avatar"
android:elevation="2dp"
android:paddingBottom="4dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="4dp"
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
android:id="@+id/text_name"
android:id="@+id/text_user_name"
style="@style/Sender.Name.TextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@+id/account_expand"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="@+id/image_user_status"
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" />
<TextView
android:id="@+id/text_server"
android:id="@+id/text_server_url"
android:layout_width="0dp"
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: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_constraintTop_toBottomOf="@+id/text_name"
app:layout_constraintTop_toBottomOf="@+id/text_user_name"
tools:text="https://open.rocket.chat" />
<ImageView
android:id="@+id/account_expand"
android:id="@+id/image_account_expand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_expand_more_24dp"
android:tint="@color/whitesmoke"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="@+id/text_server_url"
app:layout_constraintEnd_toEndOf="parent" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -13,6 +13,7 @@
<string name="title_settings">सेटिंग्स</string>
<string name="title_password">पासवर्ड बदलें</string>
<string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string>
<string name="title_about">परिचय</string>
<!-- Actions -->
<string name="action_connect">जुडिये</string>
......@@ -28,10 +29,15 @@
<string name="action_confirm_password">पासवर्ड परिवर्तन की पुष्टि करें</string>
<string name="action_join_chat">चैट में शामिल हों</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 -->
<string-array name="settings_actions">
<item name="item_password">पासवर्ड बदलें</item>
<item name="item_password">परिचय</item>
</string-array>
<!-- Regular information messages -->
......@@ -82,6 +88,8 @@
<string name="msg_ver_not_minimum">
ऐसा लगता है कि आपका सर्वर संस्करण न्यूनतम आवश्यक संस्करण %1$s से कम है।\nकृपया लॉगिन करने के लिए अपने सर्वर को अपग्रेड करें!
</string>
<string name="msg_version">वर्शन</string>
<string name="msg_build">बिल्ड</string>
<!-- System messages -->
<string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string>
......
......@@ -13,6 +13,7 @@
<string name="title_settings">Configurações</string>
<string name="title_password">Alterar senha</string>
<string name="title_update_profile">Editar perfil</string>
<string name="title_about">Sobre</string>
<!-- Actions -->
<string name="action_connect">Conectar</string>
......@@ -27,11 +28,16 @@
<string name="action_files">Arquivos</string>
<string name="action_confirm_password">Confirme a nova senha</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 -->
<string-array name="settings_actions">
<item name="item_password">Alterar senha</item>
<item name="item_password">Sobre</item>
</string-array>
<!-- Regular information messages -->
......@@ -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_2fa_code">Código 2FA</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_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_invalid_file">Arquivo inválido</string>
......@@ -75,6 +81,8 @@
<string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Foto</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_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.
......
......@@ -21,6 +21,8 @@
<dimen name="message_time_text_size">12sp</dimen>
<dimen name="nav_header_height">140dp</dimen>
<!-- Emoji -->
<dimen name="picker_padding_bottom">16dp</dimen>
<dimen name="supposed_keyboard_height">252dp</dimen>
......@@ -35,6 +37,5 @@
<!-- Autocomplete Popup -->
<dimen name="popup_max_height">150dp</dimen>
<dimen name="suggestions_box_max_height">250dp</dimen>
<dimen name="nav_header_height">160dp</dimen>
</resources>
\ No newline at end of file
......@@ -14,6 +14,7 @@
<string name="title_settings">Settings</string>
<string name="title_password">Change Password</string>
<string name="title_update_profile">Update profile</string>
<string name="title_about">About</string>
<!-- Actions -->
<string name="action_connect">Connect</string>
......@@ -28,11 +29,16 @@
<string name="action_files">Files</string>
<string name="action_confirm_password">Confirm Password Change</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 -->
<string-array name="settings_actions">
<item name="item_password">Change Password</item>
<item name="item_password">About</item>
</string-array>
<!-- Regular information messages -->
......@@ -77,6 +83,8 @@
<string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Photo</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_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>
......
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