Commit 571f80bf authored by Aniket Singh's avatar Aniket Singh

Merge branch 'develop-2.x' into aniket/feat/create-new-channel

parents 2a85aef4 34818d4b
# Mobile Team Releases Planning
This document describes the suggested method by which our mobile teams should plan for releases.
**What’s a release?**
The release is every version of the app that’s sent to the store as a production release. The release is always a major/minor update, like from (1.0.0 to 1.1.0 or to 2.0.0). Patch releases (from 1.0.0 to 1.0.1) won’t follow these steps and are considered hotfixes releases. Our versioning is following the [Semantic Versioning 2.0.0](https://semver.org) guide.
**What are the important days of a release?**
The first release candidate (TestFlight and Beta) needs be done by 27th of each month. The release will always happen on day 5th of each month, unless there’s some critical crash/bug happening.
**What happens if something could not be done in time for the release candidate?**
In general, if it’s a new feature, it’ll be postponed for the next release only. Under extreme circumstances, when it will result in significant business impact a extraordinary release could happen.
**When do we plan the release features/improvements/bugs?**
Every month can be a different day between 27th and 5th to plan the next release. The leader of the team will schedule the session and all the team will be able to participate in the planning. At this moment, most of the issues will be assigned to each member of the team.
Example (in April, 2018):
| Day | Description |
|------------|-------------------------|
| 27th Mar ~ 5th Apr | Planning new cycle |
| 5th Apr | Start new release cycle |
| 27th Apr | Release candidate |
| 5th May | Production release |
**How do we organize a release?**
Every release is a Project in GitHub. There are 6 boards on each project:
- **Desirable (temporary):** what we want to have on the release. This is very useful while planning. This is where everybody can add features/improvements that wanna see on the release;
- **Blocked:** when something is blocked (waiting asset, waiting API, etc) the issue will be on this board;
- **To-do:** after planning, all to-do issues come here;
- **In progress:** when something is in progress, the issue/PR will be on this board;
- **Review/QA:** when something is done and waiting for review or waiting to be tested, the issue/PR will be on this board;
- **Done:** when the issue is closed (merged), the issue/PR will be on this board;
**What happens when the release candidate is shipped?**
All changes in develop needs to be merged into the branch beta at this point. A new tag needs to be created following the pattern: “2.1.0-beta1”.
**What happens if there’s no bug/crash on the release candidate?**
That’s great, congrats! This time can be used in a creative way: write more tests, code maintenance that sometimes is required, resolving issues to the next release, planning, ideas and experiments.
**What happens when the release is done?**
Project and milestones are closed, all the changes are merged to the branch master and the tag is created, following the release’s pattern of the repository.
## Hotfix Releases
**When a hotfix release happen?**
Hotfix release will happen when a critical bug or crash is found in the production version of the app.
**How to handle hotfix releases?**
Simply open an issue on GitHub describing the issue, the issue is usually closed from a pull-request getting merged and a new milestone is created with the minor update, including all PRs required to the hotfix be completed. Once the milestone is completed, it can be closed and the release tag can be created.
...@@ -19,18 +19,12 @@ Since both the versions use `kotlin` for some or all of their classes, following ...@@ -19,18 +19,12 @@ Since both the versions use `kotlin` for some or all of their classes, following
- After checking out to `develop` branch as mentioned above, simply import the project in Android Studio. - After checking out to `develop` branch as mentioned above, simply import the project in Android Studio.
#### v2+ #### v2+
- This version requires the [Kotlin SDK](https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK) for Rocket.Chat. Clone the Kotlin SDK in the **same directory** as the android repository by running `git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git`. Make sure that the android repository and the kotlin sdk have the same immediate parent directory. - This version requires the [Kotlin SDK](https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK) for Rocket.Chat. Clone the Kotlin SDK in by running `git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git`.
- First, a build is required for the SDK. Change your directory to the SDK directory by running `cd Rocket.Chat.Kotlin.SDK/` in your terminal. Any of the following approaches can be followed to successfully build the SDK. - First, a build is required for the SDK, so that required jar files are generated. Make sure that the android repository and the kotlin sdk have the same immediate parent directory. Change the current directory to `Rocket.Chat.Android/app` and run the `build-sdk.sh` which will result in creating of the required jar file `core*.jar` and `common*.jar` in `Rocket.Chat.Android/app/libs`,by the following steps in your terminal window:
- **Command Line** - (Within the kotlin SDK directory) Run `./gradlew clean && ./gradlew assemble` to successfully build the project.
- **Android Studio** - Import the project in Android Studio. Go to `Build > Make Project` to build the SDK successfully.
After following the above methods, follow the following steps in your terminal window:
``` ```
cd .. cd Rocket.Chat.Android/app
cd Rocket.Chat.Android/app/libs ./build-sdk.sh
ls
``` ```
Two `jar` files will be found in this directory (the `common` and `core` jar files), this indicates that the SDK was built correctly.
- After the SDK has been built successfully, import the project in Android Studio and build it by following `Build > Make Project`.
## How to run ## How to run
### Command Line ### Command Line
......
...@@ -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 2008
versionName "2.0.0-beta3" versionName "2.0.0-beta6"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission <permission
...@@ -20,7 +21,6 @@ ...@@ -20,7 +21,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,31 +34,25 @@ ...@@ -34,31 +34,25 @@
<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:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" /> 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:theme="@style/AppTheme" android:theme="@style/AppTheme"
...@@ -71,12 +65,10 @@ ...@@ -71,12 +65,10 @@
<activity android:name=".createChannel.addMembers.ui.AddMembersActivity" <activity android:name=".createChannel.addMembers.ui.AddMembersActivity"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:windowSoftInputMode="stateAlwaysHidden" /> android:windowSoftInputMode="stateAlwaysHidden" />
<activity <activity
android:name=".chatroom.ui.PinnedMessagesActivity" android:name=".chatroom.ui.PinnedMessagesActivity"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" /> 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" />
...@@ -93,6 +85,15 @@ ...@@ -93,6 +85,15 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".push.DirectReplyReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="chat.rocket.android.ACTION_REPLY" />
</intent-filter>
</receiver>
<service <service
android:name=".push.FirebaseTokenService" android:name=".push.FirebaseTokenService"
android:exported="false"> android:exported="false">
...@@ -100,7 +101,6 @@ ...@@ -100,7 +101,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">
...@@ -112,6 +112,9 @@ ...@@ -112,6 +112,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,19 +106,21 @@ object DrawableHelper { ...@@ -106,19 +106,21 @@ 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)
} }
return userStatusDrawable
} }
// TODO Why we need to UserStatus? // TODO Why we need two UserStatus?
/** /**
* Returns the user status drawable. * Returns the user status drawable.
......
...@@ -6,7 +6,7 @@ import android.app.Service ...@@ -6,7 +6,7 @@ import android.app.Service
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.content.edit import androidx.core.content.edit
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.app.migration.RealmMigration import chat.rocket.android.app.migration.RealmMigration
import chat.rocket.android.app.migration.RocketChatLibraryModule import chat.rocket.android.app.migration.RocketChatLibraryModule
...@@ -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.infraestructure package chat.rocket.android.authentication.infraestructure
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.content.edit import androidx.core.content.edit
import chat.rocket.android.authentication.domain.model.TokenModel import chat.rocket.android.authentication.domain.model.TokenModel
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
......
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.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 +99,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -103,7 +99,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 +114,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -118,7 +114,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
...@@ -191,11 +189,11 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -191,11 +189,11 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private fun doAuthentication(loginType: Int) { private fun doAuthentication(loginType: Int) {
launchUI(strategy) { launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) { view.disableUserInput()
view.disableUserInput() view.showLoading()
view.showLoading() try {
try { val token = retryIO("login") {
val token = when (loginType) { when (loginType) {
TYPE_LOGIN_USER_EMAIL -> { TYPE_LOGIN_USER_EMAIL -> {
if (usernameOrEmail.isEmail()) { if (usernameOrEmail.isEmail()) {
client.loginWithEmail(usernameOrEmail, password) client.loginWithEmail(usernameOrEmail, password)
...@@ -218,35 +216,33 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -218,35 +216,33 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
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 }
if (username != null) { val username = retryIO("me()") { client.me().username }
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username) if (username != null) {
saveAccount(username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
saveToken(token) saveAccount(username)
registerPushToken() saveToken(token)
navigator.toChatList() registerPushToken()
} else if (loginType == TYPE_LOGIN_OAUTH) { navigator.toChatList()
navigator.toRegisterUsername(token.userId, token.authToken) } else if (loginType == TYPE_LOGIN_OAUTH) {
navigator.toRegisterUsername(token.userId, token.authToken)
}
} catch (exception: RocketChatException) {
when (exception) {
is RocketChatTwoFactorException -> {
navigator.toTwoFA(usernameOrEmail, password)
} }
} catch (exception: RocketChatException) { else -> {
when (exception) { exception.message?.let {
is RocketChatTwoFactorException -> { view.showMessage(it)
navigator.toTwoFA(usernameOrEmail, password) }.ifNull {
} view.showGenericErrorMessage()
else -> {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} }
} }
} finally {
view.hideLoading()
view.enableUserInput()
} }
} else { } finally {
view.showNoInternetConnection() view.hideLoading()
view.enableUserInput()
} }
} }
} }
...@@ -258,12 +254,12 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -258,12 +254,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)
} }
......
package chat.rocket.android.authentication.login.presentation package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.server.presentation.VersionCheckView import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface LoginView : LoadingView, MessageView, InternetView, VersionCheckView { interface LoginView : LoadingView, MessageView, VersionCheckView {
/** /**
* Shows the form view (i.e the username/email and password fields) if it is enabled by the server settings. * Shows the form view (i.e the username/email and password fields) if it is enabled by the server settings.
......
...@@ -105,10 +105,6 @@ class LoginFragment : Fragment(), LoginView { ...@@ -105,10 +105,6 @@ class LoginFragment : Fragment(), LoginView {
view_loading.setVisible(false) view_loading.setVisible(false)
} }
override fun showNoInternetConnection() {
showMessage(R.string.msg_no_internet_connection)
}
override fun showMessage(resId: Int) { override fun showMessage(resId: Int) {
showToast(resId) showToast(resId)
} }
......
...@@ -2,14 +2,15 @@ package chat.rocket.android.authentication.registerusername.presentation ...@@ -2,14 +2,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.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
...@@ -38,28 +39,26 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -38,28 +39,26 @@ class RegisterUsernamePresenter @Inject constructor(
view.alertBlankUsername() view.alertBlankUsername()
} else { } else {
launchUI(strategy) { launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) { view.showLoading()
view.showLoading() try {
try { val me = retryIO("updateOwnBasicInformation(username = $username)") {
val me = client.updateOwnBasicInformation(username = username) client.updateOwnBasicInformation(username = username)
val registeredUsername = me.username
if (registeredUsername != null) {
saveAccount(registeredUsername)
tokenRepository.save(currentServer, Token(userId, authToken))
registerPushToken()
navigator.toChatList()
}
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
} }
} else { val registeredUsername = me.username
view.showNoInternetConnection() if (registeredUsername != null) {
saveAccount(registeredUsername)
tokenRepository.save(currentServer, Token(userId, authToken))
registerPushToken()
navigator.toChatList()
}
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
} }
} }
} }
...@@ -75,12 +74,12 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -75,12 +74,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)
} }
......
package chat.rocket.android.authentication.registerusername.presentation package chat.rocket.android.authentication.registerusername.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface RegisterUsernameView : LoadingView, MessageView, InternetView { interface RegisterUsernameView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank username. * Alerts the user about a blank username.
......
...@@ -85,10 +85,6 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -85,10 +85,6 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
showMessage(getString(R.string.msg_generic_error)) showMessage(getString(R.string.msg_generic_error))
} }
override fun showNoInternetConnection() {
showMessage(getString(R.string.msg_no_internet_connection))
}
private fun tintEditTextDrawableStart() { private fun tintEditTextDrawableStart() {
activity?.apply { activity?.apply {
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this) val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this)
......
...@@ -2,11 +2,10 @@ package chat.rocket.android.authentication.server.presentation ...@@ -2,11 +2,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.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 +17,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView, ...@@ -18,7 +17,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) {
...@@ -29,23 +28,19 @@ class ServerPresenter @Inject constructor(private val view: ServerView, ...@@ -29,23 +28,19 @@ class ServerPresenter @Inject constructor(private val view: ServerView,
return@launchUI return@launchUI
} }
if (NetworkHelper.hasInternetAccess()) { view.showLoading()
view.showLoading() try {
try { refreshSettingsInteractor.refresh(server)
refreshSettingsInteractor.refresh(server) serverInteractor.save(server)
serverInteractor.save(server) navigator.toLogin()
navigator.toLogin() } catch (ex: Exception) {
} catch (ex: Exception) { ex.message?.let {
ex.message?.let { view.showMessage(it)
view.showMessage(it) }.ifNull {
}.ifNull { view.showGenericErrorMessage()
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
} }
} else { } finally {
view.showNoInternetConnection() view.hideLoading()
} }
} }
} }
......
package chat.rocket.android.authentication.server.presentation package chat.rocket.android.authentication.server.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface ServerView : LoadingView, MessageView, InternetView { interface ServerView : LoadingView, MessageView {
/** /**
* Shows an invalid server URL message. * Shows an invalid server URL message.
......
...@@ -68,10 +68,6 @@ class ServerFragment : Fragment(), ServerView { ...@@ -68,10 +68,6 @@ class ServerFragment : Fragment(), ServerView {
showMessage(getString(R.string.msg_generic_error)) showMessage(getString(R.string.msg_generic_error))
} }
override fun showNoInternetConnection() {
showMessage(getString(R.string.msg_no_internet_connection))
}
private fun enableUserInput(value: Boolean) { private fun enableUserInput(value: Boolean) {
button_connect.isEnabled = value button_connect.isEnabled = value
text_server_url.isEnabled = value text_server_url.isEnabled = value
......
...@@ -2,21 +2,22 @@ package chat.rocket.android.authentication.signup.presentation ...@@ -2,21 +2,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.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
...@@ -55,31 +56,26 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -55,31 +56,26 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
else -> { else -> {
val client = factory.create(server) val client = factory.create(server)
launchUI(strategy) { launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) { view.showLoading()
view.showLoading() try {
// TODO This function returns a user so should we save it?
try { retryIO("signup") { client.signup(email, name, username, password) }
// TODO This function returns a user so should we save it? // TODO This function returns a user token so should we save it?
client.signup(email, name, username, password) retryIO("login") { client.login(username, password) }
// TODO This function returns a user token so should we save it? val me = retryIO("me") { client.me() }
client.login(username, password) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
val me = client.me() saveAccount(me)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) registerPushToken()
saveAccount(me) navigator.toChatList()
registerPushToken() } catch (exception: RocketChatException) {
navigator.toChatList() exception.message?.let {
} catch (exception: RocketChatException) { view.showMessage(it)
exception.message?.let { }.ifNull {
view.showMessage(it) view.showGenericErrorMessage()
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
} }
} else { } finally {
view.showNoInternetConnection() view.hideLoading()
} }
} }
} }
...@@ -88,13 +84,13 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -88,13 +84,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 +104,12 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -108,12 +104,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)
} }
......
package chat.rocket.android.authentication.signup.presentation package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface SignupView : LoadingView, MessageView, InternetView { interface SignupView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank name. * Alerts the user about a blank name.
......
...@@ -109,10 +109,6 @@ class SignupFragment : Fragment(), SignupView { ...@@ -109,10 +109,6 @@ class SignupFragment : Fragment(), SignupView {
showMessage(getString(R.string.msg_generic_error)) showMessage(getString(R.string.msg_generic_error))
} }
override fun showNoInternetConnection() {
Toast.makeText(activity, getString(R.string.msg_no_internet_connection), Toast.LENGTH_SHORT).show()
}
private fun tintEditTextDrawableStart() { private fun tintEditTextDrawableStart() {
activity?.apply { activity?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, this) val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, this)
......
...@@ -2,14 +2,15 @@ package chat.rocket.android.authentication.twofactor.presentation ...@@ -2,14 +2,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.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
...@@ -46,32 +47,29 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -46,32 +47,29 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
else -> { else -> {
launchUI(strategy) { launchUI(strategy) {
val client = factory.create(server) val client = factory.create(server)
if (NetworkHelper.hasInternetAccess()) { view.showLoading()
view.showLoading() try {
try { // The token is saved via the client TokenProvider
// The token is saved via the client TokenProvider val token = retryIO("login") {
val token = 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()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) { if (exception is RocketChatAuthException) {
view.alertInvalidTwoFactorAuthenticationCode() view.alertInvalidTwoFactorAuthenticationCode()
} else { } else {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
}
} }
} finally {
view.hideLoading()
} }
} else { } finally {
view.showNoInternetConnection() view.hideLoading()
} }
} }
} }
...@@ -90,12 +88,12 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -90,12 +88,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)
} }
......
package chat.rocket.android.authentication.twofactor.presentation package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.core.behaviours.InternetView
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface TwoFAView : LoadingView, MessageView, InternetView { interface TwoFAView : LoadingView, MessageView {
/** /**
* Alerts the user about a blank Two Factor Authentication code. * Alerts the user about a blank Two Factor Authentication code.
......
...@@ -91,8 +91,6 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -91,8 +91,6 @@ class TwoFAFragment : Fragment(), TwoFAView {
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showNoInternetConnection() = showMessage(getString(R.string.msg_no_internet_connection))
private fun tintEditTextDrawableStart() { private fun tintEditTextDrawableStart() {
activity?.apply { activity?.apply {
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, this) val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, this)
......
package chat.rocket.android.chatroom.adapter
import android.content.Intent
import android.net.Uri
import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isVisible
import chat.rocket.android.chatroom.viewmodel.AuthorAttachmentViewModel
import chat.rocket.android.util.extensions.content
import chat.rocket.android.widget.emoji.EmojiReactionListener
import chat.rocket.common.util.ifNull
import kotlinx.android.synthetic.main.item_author_attachment.view.*
class AuthorAttachmentViewHolder(itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null)
: BaseViewHolder<AuthorAttachmentViewModel>(itemView, listener, reactionListener) {
init {
with(itemView) {
setupActionMenu(author_attachment_container)
setupActionMenu(text_fields)
setupActionMenu(text_author_name)
}
}
override fun bindViews(data: AuthorAttachmentViewModel) {
with(itemView) {
data.icon?.let { icon ->
author_icon.isVisible = true
author_icon.setImageURI(icon)
}.ifNull {
author_icon.isGone = true
}
author_icon.setImageURI(data.icon)
text_author_name.content = data.name
data.fields?.let { fields ->
text_fields.content = fields
text_fields.isVisible = true
}.ifNull {
text_fields.isGone = true
}
text_author_name.setOnClickListener {
it.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(data.attachmentUrl)))
}
}
}
}
\ No newline at end of file
...@@ -53,6 +53,10 @@ class ChatRoomAdapter( ...@@ -53,6 +53,10 @@ class ChatRoomAdapter(
val view = parent.inflate(R.layout.item_message_attachment) val view = parent.inflate(R.layout.item_message_attachment)
MessageAttachmentViewHolder(view, actionsListener, reactionListener) MessageAttachmentViewHolder(view, actionsListener, reactionListener)
} }
BaseViewModel.ViewType.AUTHOR_ATTACHMENT -> {
val view = parent.inflate(R.layout.item_author_attachment)
AuthorAttachmentViewHolder(view, actionsListener, reactionListener)
}
else -> { else -> {
throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}") throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}")
} }
...@@ -92,6 +96,7 @@ class ChatRoomAdapter( ...@@ -92,6 +96,7 @@ class ChatRoomAdapter(
is VideoAttachmentViewHolder -> holder.bind(dataSet[position] as VideoAttachmentViewModel) is VideoAttachmentViewHolder -> holder.bind(dataSet[position] as VideoAttachmentViewModel)
is UrlPreviewViewHolder -> holder.bind(dataSet[position] as UrlPreviewViewModel) is UrlPreviewViewHolder -> holder.bind(dataSet[position] as UrlPreviewViewModel)
is MessageAttachmentViewHolder -> holder.bind(dataSet[position] as MessageAttachmentViewModel) is MessageAttachmentViewHolder -> holder.bind(dataSet[position] as MessageAttachmentViewModel)
is AuthorAttachmentViewHolder -> holder.bind(dataSet[position] as AuthorAttachmentViewModel)
} }
} }
...@@ -100,6 +105,7 @@ class ChatRoomAdapter( ...@@ -100,6 +105,7 @@ class ChatRoomAdapter(
return when (model) { return when (model) {
is MessageViewModel -> model.messageId.hashCode().toLong() is MessageViewModel -> model.messageId.hashCode().toLong()
is BaseFileAttachmentViewModel -> model.id is BaseFileAttachmentViewModel -> model.id
is AuthorAttachmentViewModel -> model.id
else -> return position.toLong() else -> return position.toLong()
} }
} }
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.AuthorAttachment
data class AuthorAttachmentViewModel(
override val attachmentUrl: String,
val id: Long,
val name: CharSequence?,
val icon: String?,
val fields: CharSequence?,
override val message: Message,
override val rawData: AuthorAttachment,
override val messageId: String,
override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null
) : BaseAttachmentViewModel<AuthorAttachment> {
override val viewType: Int
get() = BaseViewModel.ViewType.AUTHOR_ATTACHMENT.viewType
override val layoutId: Int
get() = R.layout.item_author_attachment
}
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel
interface BaseAttachmentViewModel<out T> : BaseViewModel<T> {
val attachmentUrl: String
}
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel package chat.rocket.android.chatroom.viewmodel
interface BaseFileAttachmentViewModel<out T> : BaseViewModel<T> { interface BaseFileAttachmentViewModel<out T> : BaseAttachmentViewModel<T> {
val attachmentUrl: String
val attachmentTitle: CharSequence val attachmentTitle: CharSequence
val id: Long val id: Long
} }
\ No newline at end of file
...@@ -20,7 +20,8 @@ interface BaseViewModel<out T> { ...@@ -20,7 +20,8 @@ interface BaseViewModel<out T> {
IMAGE_ATTACHMENT(3), IMAGE_ATTACHMENT(3),
VIDEO_ATTACHMENT(4), VIDEO_ATTACHMENT(4),
AUDIO_ATTACHMENT(5), AUDIO_ATTACHMENT(5),
MESSAGE_ATTACHMENT(6) MESSAGE_ATTACHMENT(6),
AUTHOR_ATTACHMENT(7)
} }
} }
......
...@@ -4,14 +4,20 @@ import DateTimeHelper ...@@ -4,14 +4,20 @@ import DateTimeHelper
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.support.v4.content.ContextCompat
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.text.color
import androidx.core.text.scale
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.util.extensions.isNotNullNorEmpty
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
...@@ -39,6 +45,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -39,6 +45,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
private val baseUrl = settings.baseUrl() private val baseUrl = settings.baseUrl()
private val token = tokenRepository.get(currentServer) private val token = tokenRepository.get(currentServer)
private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
private val secundaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
suspend fun map(message: Message): List<BaseViewModel<*>> { suspend fun map(message: Message): List<BaseViewModel<*>> {
return translate(message) return translate(message)
...@@ -68,8 +75,8 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -68,8 +75,8 @@ class ViewModelMapper @Inject constructor(private val context: Context,
} }
mapMessage(message).let { mapMessage(message).let {
if (list.size > 0) { if (list.isNotEmpty()) {
it.preview = list[0].preview it.preview = list.first().preview
} }
list.add(it) list.add(it)
} }
...@@ -98,10 +105,39 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -98,10 +105,39 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return when (attachment) { return when (attachment) {
is FileAttachment -> mapFileAttachment(message, attachment) is FileAttachment -> mapFileAttachment(message, attachment)
is MessageAttachment -> mapMessageAttachment(message, attachment) is MessageAttachment -> mapMessageAttachment(message, attachment)
is AuthorAttachment -> mapAuthorAttachment(message, attachment)
else -> null else -> null
} }
} }
private suspend fun mapAuthorAttachment(message: Message, attachment: AuthorAttachment): AuthorAttachmentViewModel {
return with(attachment) {
val content = stripMessageQuotes(message)
val fieldsText = fields?.let {
buildSpannedString {
it.forEachIndexed { index, field ->
bold { append(field.title) }
append("\n")
if (field.value.isNotEmpty()) {
append(field.value)
}
if (index != it.size - 1) { // it is not the last one, append a new line
append("\n\n")
}
}
}
}
val id = attachmentId(message, attachment)
AuthorAttachmentViewModel(attachmentUrl = url, id = id, name = authorName,
icon = authorIcon, fields = fieldsText, message = message, rawData = attachment,
messageId = message.id, reactions = getReactions(message),
preview = message.copy(message = content.message))
}
}
private suspend fun mapMessageAttachment(message: Message, attachment: MessageAttachment): MessageAttachmentViewModel { private suspend fun mapMessageAttachment(message: Message, attachment: MessageAttachment): MessageAttachmentViewModel {
val attachmentAuthor = attachment.author val attachmentAuthor = attachment.author
val time = attachment.timestamp?.let { getTime(it) } val time = attachment.timestamp?.let { getTime(it) }
...@@ -136,7 +172,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -136,7 +172,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
} }
} }
private fun attachmentId(message: Message, attachment: FileAttachment): Long { private fun attachmentId(message: Message, attachment: Attachment): Long {
return "${message.id}_${attachment.url}".hashCode().toLong() return "${message.id}_${attachment.url}".hashCode().toLong()
} }
...@@ -213,16 +249,26 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -213,16 +249,26 @@ class ViewModelMapper @Inject constructor(private val context: Context,
private suspend fun stripMessageQuotes(message: Message): Message { private suspend fun stripMessageQuotes(message: Message): Message {
val baseUrl = settings.baseUrl() val baseUrl = settings.baseUrl()
return message.copy( return message.copy(
message = message.message.replace("\\[\\s\\]\\($baseUrl.*\\)".toRegex(), "").trim() message = message.message.replace("\\[[^\\]]+\\]\\($baseUrl[^)]+\\)".toRegex(), "").trim()
) )
} }
private fun getSenderName(message: Message): CharSequence { private fun getSenderName(message: Message): CharSequence {
if (!message.senderAlias.isNullOrEmpty()) { val username = message.sender?.username
return message.senderAlias!! message.senderAlias.isNotNullNorEmpty { alias ->
return buildSpannedString {
append(alias)
username?.let {
append(" ")
scale(0.8f) {
color(secundaryTextColor) {
append("@$username")
}
}
}
}
} }
val username = message.sender?.username
val realName = message.sender?.name val realName = message.sender?.name
val senderName = if (settings.useRealName()) realName else username val senderName = if (settings.useRealName()) realName else username
return senderName ?: username.toString() return senderName ?: username.toString()
...@@ -235,7 +281,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -235,7 +281,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
...@@ -74,14 +74,17 @@ class ChatRoomsAdapter(private val context: Context, ...@@ -74,14 +74,17 @@ 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}" if (chatRoom.type is RoomType.DirectMessage) {
drawee.setImageURI(UrlHelper.getAvatarUrl(chatRoom.client.url, avatarId)) drawee.setImageURI(chatRoom.client.url.avatarUrl(chatRoom.name))
} else {
drawee.setImageURI(chatRoom.client.url.avatarUrl(chatRoom.name, true))
}
} }
private fun bindName(chatRoom: ChatRoom, textView: TextView) { private fun bindName(chatRoom: ChatRoom, textView: TextView) {
textView.textContent = chatRoom.name textView.textContent = chatRoom.name
var drawable = when (chatRoom.type) { val drawable = when (chatRoom.type) {
is RoomType.Channel -> { is RoomType.Channel -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_channel, context) DrawableHelper.getDrawableFromId(R.drawable.ic_room_channel, context)
} }
......
...@@ -161,15 +161,16 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -161,15 +161,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.core.behaviours
interface InternetView {
fun showNoInternetConnection()
}
\ No newline at end of file
...@@ -5,7 +5,7 @@ import android.app.NotificationManager ...@@ -5,7 +5,7 @@ import android.app.NotificationManager
import android.arch.persistence.room.Room import android.arch.persistence.room.Room
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.content.systemService import androidx.core.content.systemService
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.app.RocketChatDatabase import chat.rocket.android.app.RocketChatDatabase
...@@ -17,17 +17,8 @@ import chat.rocket.android.helper.MessageParser ...@@ -17,17 +17,8 @@ import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPrefsLocalRepository import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.push.GroupedPush import chat.rocket.android.push.GroupedPush
import chat.rocket.android.server.domain.AccountsRepository import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.ChatRoomsRepository import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetPermissionsInteractor
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.RoomRepository
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.MemoryMessagesRepository import chat.rocket.android.server.infraestructure.MemoryMessagesRepository
import chat.rocket.android.server.infraestructure.MemoryRoomRepository import chat.rocket.android.server.infraestructure.MemoryRoomRepository
...@@ -275,4 +266,16 @@ class AppModule { ...@@ -275,4 +266,16 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideGroupedPush() = GroupedPush() fun provideGroupedPush() = GroupedPush()
@Provides
@Singleton
fun providePushManager(
context: Context,
groupedPushes: GroupedPush,
manager: NotificationManager,
moshi: Moshi,
getAccountInteractor: GetAccountInteractor,
getSettingsInteractor: GetSettingsInteractor): PushManager {
return PushManager(groupedPushes, manager, moshi, getAccountInteractor, getSettingsInteractor, context)
}
} }
\ No newline at end of file
package chat.rocket.android.dagger.module package chat.rocket.android.dagger.module
import chat.rocket.android.push.DeleteReceiver import chat.rocket.android.push.DeleteReceiver
import chat.rocket.android.push.DirectReplyReceiver
import chat.rocket.android.push.DirectReplyReceiverProvider
import chat.rocket.android.push.di.DeleteReceiverProvider import chat.rocket.android.push.di.DeleteReceiverProvider
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -10,4 +12,7 @@ abstract class ReceiverBuilder { ...@@ -10,4 +12,7 @@ abstract class ReceiverBuilder {
@ContributesAndroidInjector(modules = [DeleteReceiverProvider::class]) @ContributesAndroidInjector(modules = [DeleteReceiverProvider::class])
abstract fun bindDeleteReceiver(): DeleteReceiver abstract fun bindDeleteReceiver(): DeleteReceiver
@ContributesAndroidInjector(modules = [DirectReplyReceiverProvider::class])
abstract fun bindDirectReplyReceiver(): DirectReplyReceiver
} }
\ No newline at end of file
package chat.rocket.android.helper
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Socket
object NetworkHelper {
/**
* Checks whether there is internet access.
*
* The original author of this code is Levit and you can see his answer here: https://stackoverflow.com/a/27312494/4744263
*
* @return true if there is internet access, false otherwise.
*/
suspend fun hasInternetAccess(): Boolean = withContext(CommonPool) {
try {
val socket = Socket()
val inetSocketAddress = InetSocketAddress("8.8.8.8", 53)
socket.connect(inetSocketAddress, 1500)
socket.close()
true
} catch (e: IOException) {
false
}
}
}
\ No newline at end of file
package chat.rocket.android.helper 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
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,6 +11,8 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -12,6 +11,8 @@ 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
...@@ -21,6 +22,10 @@ import chat.rocket.core.internal.realtime.setDefaultStatus ...@@ -21,6 +22,10 @@ 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
import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -44,6 +49,8 @@ class MainPresenter @Inject constructor( ...@@ -44,6 +49,8 @@ class MainPresenter @Inject constructor(
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!)
private val userDataChannel = Channel<Myself>()
fun toChatList() = navigator.toChatList() fun toChatList() = navigator.toChatList()
fun toUserProfile() = navigator.toUserProfile() fun toUserProfile() = navigator.toUserProfile()
...@@ -54,9 +61,10 @@ class MainPresenter @Inject constructor( ...@@ -54,9 +61,10 @@ 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) {
...@@ -74,17 +82,10 @@ class MainPresenter @Inject constructor( ...@@ -74,17 +82,10 @@ class MainPresenter @Inject constructor(
} }
} }
} }
subscribeMyselfUpdates()
} }
} }
private suspend fun saveAccount(me: NavHeaderViewModel) {
val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it)
}
val account = Account(currentServer, icon, me.serverLogo, me.username, me.avatar)
saveAccountInteractor.save(account)
}
/** /**
* Logout from current server. * Logout from current server.
*/ */
...@@ -92,7 +93,7 @@ class MainPresenter @Inject constructor( ...@@ -92,7 +93,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 {
...@@ -115,20 +116,12 @@ class MainPresenter @Inject constructor( ...@@ -115,20 +116,12 @@ class MainPresenter @Inject constructor(
} }
} }
private suspend fun clearTokens() {
serverInteractor.clear()
val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN)
if (pushToken != null) {
client.unregisterPushToken(pushToken)
}
localRepository.clearAllFromServer(currentServer)
}
fun connect() { fun connect() {
manager.connect() manager.connect()
} }
fun disconnect() { fun disconnect() {
manager.removeUserDataChannel(userDataChannel)
manager.disconnect() manager.disconnect()
} }
...@@ -165,4 +158,43 @@ class MainPresenter @Inject constructor( ...@@ -165,4 +158,43 @@ class MainPresenter @Inject constructor(
client.registerPushToken(token, getAccountsInteractor.get(), factory) client.registerPushToken(token, getAccountsInteractor.get(), factory)
} }
} }
private suspend fun saveAccount(viewModel: NavHeaderViewModel) {
val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it)
}
val account = Account(
currentServer,
icon,
viewModel.serverLogo,
viewModel.userDisplayName!!,
viewModel.userAvatar
)
saveAccountInteractor.save(account)
}
private suspend fun clearTokens() {
serverInteractor.clear()
val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN)
if (pushToken != null) {
try {
retryIO("unregisterPushToken") { client.unregisterPushToken(pushToken) }
} catch (ex: Exception) {
Timber.d(ex, "Error unregistering push token")
}
}
localRepository.clearAllFromServer(currentServer)
}
private suspend fun subscribeMyselfUpdates() {
manager.addUserDataChannel(userDataChannel)
for (myself in userDataChannel) {
updateMyself(myself)
}
}
private suspend fun updateMyself(myself: Myself) {
val model = navHeaderMapper.mapToViewModel(myself)
view.setupNavHeader(model, getAccountsInteractor.get())
}
} }
\ No newline at end of file
...@@ -18,10 +18,10 @@ interface MainView : MessageView, VersionCheckView { ...@@ -18,10 +18,10 @@ interface MainView : MessageView, VersionCheckView {
/** /**
* Setups the navigation header. * Setups the navigation header.
* *
* @param model The [NavHeaderViewModel]. * @param viewModel The [NavHeaderViewModel].
* @param accounts The list of accounts. * @param accounts The list of accounts.
*/ */
fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>) fun setupNavHeader(viewModel: NavHeaderViewModel, accounts: List<Account>)
fun closeServerSelection() fun closeServerSelection()
} }
\ No newline at end of file
...@@ -92,13 +92,26 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -92,13 +92,26 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
} }
} }
override fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>) { override fun setupNavHeader(viewModel: NavHeaderViewModel, accounts: List<Account>) {
Timber.d("Setting up nav header: $model") Timber.d("Setting up nav header: $viewModel")
with(headerLayout) { with(headerLayout) {
text_user_name.text = model.username with(viewModel) {
text_server_url.text = model.server if (userStatus != null) {
image_avatar.setImageURI(model.avatar) image_user_status.setImageDrawable(
server_logo.setImageURI(model.serverLogo) DrawableHelper.getUserStatusDrawable(userStatus, context)
)
}
if (userDisplayName != null) {
text_user_name.text = userDisplayName
}
if (userAvatar != null) {
image_avatar.setImageURI(userAvatar)
}
if (serverLogo != null) {
server_logo.setImageURI(serverLogo)
}
text_server_url.text = viewModel.serverUrl
}
setupAccountsList(headerLayout, accounts) setupAccountsList(headerLayout, accounts)
} }
} }
......
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
return senderName ?: username.toString() return senderName ?: username
} }
} }
\ No newline at end of file
...@@ -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, format = "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) {
......
package chat.rocket.android.push
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.support.v4.app.NotificationManagerCompat
import android.support.v4.app.RemoteInput
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.sendMessage
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import java.util.*
import javax.inject.Inject
/**
* BroadcastReceiver for direct reply on notifications.
*/
class DirectReplyReceiver : BroadcastReceiver() {
@Inject
lateinit var factory: ConnectionManagerFactory
@Inject
lateinit var groupedPushes: GroupedPush
@Inject
lateinit var pushManager: PushManager
@Inject
lateinit var manager: NotificationManager
override fun onReceive(context: Context, intent: Intent) {
AndroidInjection.inject(this, context)
if (ACTION_REPLY == intent.action) {
val message = intent.getParcelableExtra<PushMessage>(EXTRA_PUSH_MESSAGE)
message?.let {
launch(UI) {
val notificationId = it.notificationId.toInt()
val hostname = it.info.host
try {
sendMessage(it, extractReplyMessage(intent))
clearNotificationsByHostAndNotificationId(hostname, notificationId)
manager.cancel(notificationId)
val feedback = context.getString(R.string.notif_success_sending, it.title)
Toast.makeText(context, feedback, Toast.LENGTH_SHORT).show()
} catch (ex: RocketChatException) {
Timber.e(ex)
val feedback = context.getString(R.string.notif_error_sending)
Toast.makeText(context, feedback, Toast.LENGTH_SHORT).show()
clearNotificationsByHostAndNotificationId(hostname, notificationId)
pushManager.showNotification(it)
}
}
}
}
}
private suspend fun sendMessage(message: PushMessage, replyText: CharSequence?) {
replyText?.let { reply ->
val currentServer = message.info.hostname
val roomId = message.info.roomId
val connectionManager = factory.create(currentServer)
val client = connectionManager.client
val id = UUID.randomUUID().toString()
client.sendMessage(id, roomId, reply.toString())
// Do we need to disconnect here?
}
}
private fun extractReplyMessage(intent: Intent): CharSequence? {
val bundle = RemoteInput.getResultsFromIntent(intent)
if (bundle != null) {
return bundle.getCharSequence(REMOTE_INPUT_REPLY)
}
return null
}
/**
* Clear notifications by the host they belong to and its unique id.
*/
private fun clearNotificationsByHostAndNotificationId(host: String, notificationId: Int) {
if (groupedPushes.hostToPushMessageList.isNotEmpty()) {
val notifications = groupedPushes.hostToPushMessageList[host]
notifications?.let {
notifications.removeAll {
it.notificationId.toInt() == notificationId
}
}
}
}
}
\ No newline at end of file
package chat.rocket.android.push
import chat.rocket.android.dagger.module.AppModule
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class DirectReplyReceiverProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideDirectReplyReceiver(): DirectReplyReceiver
}
\ No newline at end of file
...@@ -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,27 +5,30 @@ import chat.rocket.core.RocketChatClient ...@@ -5,27 +5,30 @@ import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.realtime.* import chat.rocket.core.internal.realtime.*
import chat.rocket.core.internal.rest.chatRooms import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList
class ConnectionManager(internal val client: RocketChatClient) { class ConnectionManager(internal val client: RocketChatClient) {
private val statusChannelList = ArrayList<Channel<State>>() private val statusChannelList = CopyOnWriteArrayList<Channel<State>>()
private val statusChannel = Channel<State>() private val statusChannel = Channel<State>()
private var connectJob: Job? = null private var connectJob: Job? = null
private val roomAndSubscriptionChannels = ArrayList<Channel<StreamMessage<BaseRoom>>>() private val roomAndSubscriptionChannels = ArrayList<Channel<StreamMessage<BaseRoom>>>()
private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>() private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>()
private val userDataChannels = ArrayList<Channel<Myself>>()
private val subscriptionIdMap = HashMap<String, String>() private val subscriptionIdMap = HashMap<String, String>()
private var subscriptionId: String? = null private var subscriptionId: String? = null
private var roomsId: String? = null private var roomsId: String? = null
private var userId: String? = null
fun connect() { fun connect() {
if (connectJob?.isActive == true if (connectJob?.isActive == true && (state !is State.Disconnected)) {
&& (state !is State.Disconnected)) {
Timber.d("Already connected, just returning...") Timber.d("Already connected, just returning...")
return return
} }
...@@ -51,6 +54,11 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -51,6 +54,11 @@ class ConnectionManager(internal val client: RocketChatClient) {
roomsId = id roomsId = id
} }
client.subscribeUserDataChanges { _, id ->
Timber.d("Subscribed to the user: $id")
userId = id
}
resubscribeRooms() resubscribeRooms()
} }
is State.Waiting -> { is State.Waiting -> {
...@@ -91,6 +99,15 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -91,6 +99,15 @@ class ConnectionManager(internal val client: RocketChatClient) {
} }
} }
launch(parent = connectJob) {
for (myself in client.userDataChannel) {
Timber.d("Got userData")
for (channel in userDataChannels) {
channel.send(myself)
}
}
}
client.connect() client.connect()
// Broadcast initial state... // Broadcast initial state...
...@@ -124,6 +141,10 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -124,6 +141,10 @@ class ConnectionManager(internal val client: RocketChatClient) {
fun removeRoomsAndSubscriptionsChannel(channel: Channel<StreamMessage<BaseRoom>>) = roomAndSubscriptionChannels.remove(channel) fun removeRoomsAndSubscriptionsChannel(channel: Channel<StreamMessage<BaseRoom>>) = roomAndSubscriptionChannels.remove(channel)
fun addUserDataChannel(channel: Channel<Myself>) = userDataChannels.add(channel)
fun removeUserDataChannel(channel: Channel<Myself>) = userDataChannels.remove(channel)
fun subscribeRoomMessages(roomId: String, channel: Channel<Message>) { fun subscribeRoomMessages(roomId: String, channel: Channel<Message>) {
val oldSub = roomMessagesChannels.put(roomId, channel) val oldSub = roomMessagesChannels.put(roomId, channel)
if (oldSub != null) { if (oldSub != null) {
......
package chat.rocket.android.server.infraestructure package chat.rocket.android.server.infraestructure
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.content.edit import androidx.core.content.edit
import chat.rocket.android.server.domain.AccountsRepository import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
......
...@@ -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
package chat.rocket.android.util.extensions
inline fun CharSequence?.isNotNullNorEmpty(block: (CharSequence) -> Unit) {
if (this != null && this.isNotEmpty()) {
block(this)
}
}
\ 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, isGroupOrChannel: Boolean = false, format: String = "jpeg"): String {
return if (isGroupOrChannel) {
"${removeTrailingSlash()}/avatar/%23${avatar.removeTrailingSlash()}?format=$format"
} else {
"${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
...@@ -9,10 +9,7 @@ import android.provider.DocumentsContract ...@@ -9,10 +9,7 @@ import android.provider.DocumentsContract
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import java.io.FileInputStream import java.io.*
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
fun Uri.getFileName(context: Context): String? { fun Uri.getFileName(context: Context): String? {
val cursor = context.contentResolver.query(this, null, null, null, null, null) val cursor = context.contentResolver.query(this, null, null, null, null, null)
...@@ -27,15 +24,21 @@ fun Uri.getFileName(context: Context): String? { ...@@ -27,15 +24,21 @@ 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)
var fileSize: String? = null var fileSize: String? = null
cursor.use { cursor -> if (scheme == ContentResolver.SCHEME_CONTENT) {
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) try {
if (cursor != null && cursor.moveToFirst()) { val fileInputStream = context.contentResolver.openInputStream(this)
if (!cursor.isNull(sizeIndex)) { fileSize = fileInputStream.available().toString()
fileSize = cursor.getString(sizeIndex) } catch (e: Exception) {
} e.printStackTrace()
}
} else if (scheme == ContentResolver.SCHEME_FILE) {
val path = this.path
try {
val f = File(path)
fileSize = f.length().toString()
} catch (e: Exception) {
e.printStackTrace()
} }
} }
return fileSize?.toIntOrNull() ?: -1 return fileSize?.toIntOrNull() ?: -1
...@@ -46,7 +49,11 @@ fun Uri.getMimeType(context: Context): String { ...@@ -46,7 +49,11 @@ fun Uri.getMimeType(context: Context): String {
context.contentResolver.getType(this) context.contentResolver.getType(this)
} else { } else {
val fileExtension = MimeTypeMap.getFileExtensionFromUrl(toString()) val fileExtension = MimeTypeMap.getFileExtensionFromUrl(toString())
MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase()) if (fileExtension != null) {
MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase())
} else {
"application/octet-stream"
}
} }
} }
......
...@@ -8,7 +8,7 @@ import android.os.Bundle ...@@ -8,7 +8,7 @@ import android.os.Bundle
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.net.toUri import androidx.core.net.toUri
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.util.extensions.decodeUrl import chat.rocket.android.util.extensions.decodeUrl
import chat.rocket.android.util.extensions.toJsonObject import chat.rocket.android.util.extensions.toJsonObject
......
...@@ -203,11 +203,6 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -203,11 +203,6 @@ class SuggestionsView : FrameLayout, TextWatcher {
completionOffset.set(NO_STATE_INDEX) completionOffset.set(NO_STATE_INDEX)
} }
collapse() collapse()
// Re-enable keyboard suggestions.
val editText = editor?.get()
if (editText != null) {
editText.inputType = editText.inputType and InputType.TYPE_TEXT_VARIATION_FILTER.inv()
}
} }
private fun insertSuggestionOnEditor(item: SuggestionModel) { private fun insertSuggestionOnEditor(item: SuggestionModel) {
......
<?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
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="@color/colorPrimary" /> <solid android:color="@color/quoteBar" />
<size <size
android:width="4dp" android:width="4dp"
android:height="4dp" /> android:height="4dp" />
......
<?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
...@@ -2,22 +2,26 @@ ...@@ -2,22 +2,26 @@
<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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
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.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/layout_new_password" android:id="@+id/layout_new_password"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="@style/EditText.Password" android:theme="@style/EditText.Password"
app:layout_constraintBottom_toTopOf="@id/middle_guide" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/start_guide" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/end_guide"> app:layout_constraintTop_toTopOf="parent"
android:layout_margin="16dp"
>
<EditText <EditText
android:id="@+id/text_new_password" android:id="@+id/text_new_password"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/msg_new_password" android:hint="@string/msg_new_password"
android:inputType="textPassword" /> android:inputType="textPassword"
/>
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
...@@ -25,9 +29,11 @@ ...@@ -25,9 +29,11 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="@style/EditText.Password" android:theme="@style/EditText.Password"
app:layout_constraintTop_toBottomOf="@id/middle_guide" app:layout_constraintTop_toBottomOf="@+id/layout_new_password"
app:layout_constraintStart_toEndOf="@id/start_guide" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/end_guide"> app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="16dp"
>
<EditText <EditText
android:id="@+id/text_confirm_password" android:id="@+id/text_confirm_password"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -40,31 +46,12 @@ ...@@ -40,31 +46,12 @@
android:id="@+id/view_loading" android:id="@+id/view_loading"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:visibility="visible"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/middle_guide"
app:layout_constraintStart_toEndOf="@id/start_guide"
app:layout_constraintEnd_toStartOf="@id/end_guide"
app:indicatorColor="@color/black" app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator" /> app:indicatorName="BallPulseIndicator"
app:layout_constraintStart_toStartOf="parent"
<android.support.constraint.Guideline app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/middle_guide" app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content" app:layout_constraintBottom_toBottomOf="parent"/>
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.2" />
<android.support.constraint.Guideline
android:id="@+id/start_guide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.05" />
<android.support.constraint.Guideline
android:id="@+id/end_guide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.95" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/author_attachment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding">
<View
android:id="@+id/quote_bar"
android:layout_width="4dp"
android:layout_height="0dp"
android:layout_marginStart="56dp"
android:background="@drawable/quote_vertical_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/recycler_view_reactions"/>
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/author_icon"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginTop="6dp"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@id/quote_bar"
tools:src="@tools:sample/avatars"/>
<TextView
android:id="@+id/text_author_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/author_icon"
app:layout_constraintEnd_toEndOf="parent"
tools:text="#5571 - User profile from SSO must not have password change option" />
<TextView
android:id="@+id/text_fields"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="@id/text_author_name"
app:layout_constraintStart_toStartOf="@id/text_author_name"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/quote_bar"
app:layout_constraintTop_toBottomOf="@id/text_fields" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
...@@ -10,45 +10,52 @@ ...@@ -10,45 +10,52 @@
android:id="@+id/text_online" android:id="@+id/text_online"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="10dp" android:paddingBottom="8dp"
android:layout_marginEnd="16dp" android:paddingEnd="16dp"
android:layout_marginStart="16dp" android:paddingStart="16dp"
android:layout_marginTop="16dp" android:paddingTop="8dp"
android:drawablePadding="10dp" android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_online_24dp" android:drawableStart="@drawable/ic_status_online_24dp"
android:text="@string/action_online" /> android:text="@string/action_online"
android:background="?selectableItemBackground"/>
<TextView <TextView
android:id="@+id/text_away" android:id="@+id/text_away"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="10dp" android:paddingBottom="8dp"
android:layout_marginEnd="16dp" android:paddingEnd="16dp"
android:layout_marginStart="16dp" android:paddingStart="16dp"
android:paddingTop="8dp"
android:drawablePadding="10dp" android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_away_24dp" android:drawableStart="@drawable/ic_status_away_24dp"
android:text="@string/action_away" /> android:text="@string/action_away"
android:background="?selectableItemBackground"/>
<TextView <TextView
android:id="@+id/text_busy" android:id="@+id/text_busy"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="10dp" android:paddingBottom="8dp"
android:layout_marginEnd="16dp" android:paddingEnd="16dp"
android:layout_marginStart="16dp" android:paddingStart="16dp"
android:paddingTop="8dp"
android:drawablePadding="10dp" android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_busy_24dp" android:drawableStart="@drawable/ic_status_busy_24dp"
android:text="@string/action_busy" /> android:text="@string/action_busy"
android:background="?selectableItemBackground"/>
<TextView <TextView
android:id="@+id/text_invisible" android:id="@+id/text_invisible"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:paddingBottom="8dp"
android:layout_marginEnd="16dp" android:paddingEnd="16dp"
android:layout_marginStart="16dp" android:paddingStart="16dp"
android:paddingTop="8dp"
android:drawablePadding="10dp" android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_status_invisible_24dp" android:drawableStart="@drawable/ic_status_invisible_24dp"
android:text="@string/action_invisible" /> android:text="@string/action_invisible"
android:background="?selectableItemBackground"/>
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -16,70 +16,51 @@ ...@@ -16,70 +16,51 @@
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<android.support.constraint.ConstraintLayout <TextView
android:id="@+id/top_container" android:id="@+id/text_chat_name"
style="@style/ChatRoom.Name.TextView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
app:layout_constraintLeft_toRightOf="@+id/layout_avatar" app:layout_constraintStart_toEndOf="@id/layout_avatar"
app:layout_constraintRight_toRightOf="parent"> android:textDirection="locale"
tools:text="General"/>
<TextView <TextView
android:id="@+id/text_chat_name" android:id="@+id/text_last_message_date_time"
style="@style/ChatRoom.Name.TextView" style="@style/Timestamp.TextView"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawablePadding="@dimen/text_view_drawable_padding" android:layout_marginStart="5dp"
app:layout_constraintLeft_toLeftOf="parent" android:layout_marginEnd="5dp"
app:layout_constraintRight_toLeftOf="@+id/text_last_message_date_time" app:layout_constraintBaseline_toBaselineOf="@+id/text_chat_name"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent"
tools:text="General" /> tools:text="11:45 AM" />
<TextView
android:id="@+id/text_last_message_date_time"
style="@style/Timestamp.TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
app:layout_constraintBaseline_toBaselineOf="@+id/text_chat_name"
app:layout_constraintLeft_toRightOf="@+id/text_chat_name"
app:layout_constraintRight_toRightOf="parent"
tools:text="11:45 AM" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout <TextView
android:id="@+id/bottom_container" android:id="@+id/text_last_message"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:ellipsize="end"
android:maxLines="2"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
app:layout_constraintLeft_toRightOf="@+id/layout_avatar" app:layout_constraintStart_toStartOf="@id/text_chat_name"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/text_chat_name"
app:layout_constraintTop_toBottomOf="@+id/top_container"> app:layout_constraintEnd_toStartOf="@id/layout_unread_messages_badge"
android:textDirection="locale"
tools:text="You: Type something"/>
<TextView <include
android:id="@+id/text_last_message" android:id="@+id/layout_unread_messages_badge"
android:layout_width="0dp" layout="@layout/unread_messages_badge"
android:layout_height="wrap_content" android:layout_width="18dp"
android:ellipsize="end" android:layout_height="18dp"
android:maxLines="2" android:layout_marginStart="5dp"
app:layout_constraintLeft_toLeftOf="parent" android:layout_marginEnd="5dp"
app:layout_constraintRight_toLeftOf="@+id/layout_unread_messages_badge" app:layout_constraintTop_toTopOf="@id/text_last_message"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent"/>
tools:text="You: Type something" />
<include
android:id="@+id/layout_unread_messages_badge"
layout="@layout/unread_messages_badge"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/text_last_message"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
\ No newline at end of file
...@@ -45,7 +45,6 @@ ...@@ -45,7 +45,6 @@
android:id="@+id/image_user_status" android:id="@+id/image_user_status"
android:layout_width="14dp" android:layout_width="14dp"
android:layout_height="14dp" android:layout_height="14dp"
android:src="@drawable/ic_status_online_24dp"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<TextView <TextView
......
...@@ -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>
...@@ -36,10 +37,10 @@ ...@@ -36,10 +37,10 @@
<!-- 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 -->
<string name="msg_no_internet_connection">कोई इंटरनेट कनेक्शन नहीं है</string>
<string name="msg_generic_error">क्षमा करें, एक त्रुटि हुई है, कृपया पुनः प्रयास करें</string> <string name="msg_generic_error">क्षमा करें, एक त्रुटि हुई है, कृपया पुनः प्रयास करें</string>
<string name="msg_no_data_to_display">डेटा प्रदर्शित करने के लिए उपलब्ध नहीं हैं</string> <string name="msg_no_data_to_display">डेटा प्रदर्शित करने के लिए उपलब्ध नहीं हैं</string>
<string name="msg_profile_update_successfully">प्रोफ़ाइल सफलतापूर्वक अपडेट हो गया है</string> <string name="msg_profile_update_successfully">प्रोफ़ाइल सफलतापूर्वक अपडेट हो गया है</string>
...@@ -86,6 +87,8 @@ ...@@ -86,6 +87,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>
...@@ -177,4 +180,9 @@ ...@@ -177,4 +180,9 @@
<string name="header_direct_messages">प्रत्यक्ष संदेश</string> <string name="header_direct_messages">प्रत्यक्ष संदेश</string>
<string name="header_live_chats">Live Chats</string> <string name="header_live_chats">Live Chats</string>
<string name="header_unknown">अज्ञात</string> <string name="header_unknown">अज्ञात</string>
<!--Notifications-->
<string name="notif_action_reply_hint">जवाब</string>
<string name="notif_error_sending">उत्तर विफल हुआ है। कृपया फिर से प्रयास करें।</string>
<string name="notif_success_sending">संदेश भेजा गया %1$s!</string>
</resources> </resources>
\ No newline at end of file
...@@ -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>
...@@ -36,10 +37,10 @@ ...@@ -36,10 +37,10 @@
<!-- 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 -->
<string name="msg_no_internet_connection">Sem conexão à internet</string>
<string name="msg_generic_error">Desculpe, ocorreu um erro, tente novamente</string> <string name="msg_generic_error">Desculpe, ocorreu um erro, tente novamente</string>
<string name="msg_no_data_to_display">Nenhum dado para exibir</string> <string name="msg_no_data_to_display">Nenhum dado para exibir</string>
<string name="msg_profile_update_successfully">Perfil atualizado com sucesso</string> <string name="msg_profile_update_successfully">Perfil atualizado com sucesso</string>
...@@ -79,6 +80,8 @@ ...@@ -79,6 +80,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.
...@@ -177,4 +180,9 @@ ...@@ -177,4 +180,9 @@
<string name="header_direct_messages">Mensagens diretas</string> <string name="header_direct_messages">Mensagens diretas</string>
<string name="header_live_chats">Live Chats</string> <string name="header_live_chats">Live Chats</string>
<string name="header_unknown">Desconhecido</string> <string name="header_unknown">Desconhecido</string>
<!--Notifications-->
<string name="notif_action_reply_hint">RESPONDER</string>
<string name="notif_error_sending">Falha ao enviar a mensagem.</string>
<string name="notif_success_sending">Mensagem enviada para %1$s!</string>
</resources> </resources>
\ No newline at end of file
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
<color name="colorEmojiIcon">#FF767676</color> <color name="colorEmojiIcon">#FF767676</color>
<color name="quoteBar">#A0A0A0</color>
<!-- Suggestions --> <!-- Suggestions -->
<color name="suggestion_background_color">@android:color/white</color> <color name="suggestion_background_color">@android:color/white</color>
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="title_update_profile">Update profile</string> <string name="title_update_profile">Update profile</string>
<string name="title_create_new_channel">Create New Channel</string> <string name="title_create_new_channel">Create New Channel</string>
<string name="title_add_members">Invite Members</string> <string name="title_add_members">Invite Members</string>
<string name="title_about">About</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Connect</string> <string name="action_connect">Connect</string>
...@@ -41,10 +42,10 @@ ...@@ -41,10 +42,10 @@
<!-- 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 -->
<string name="msg_no_internet_connection">No internet connection</string>
<string name="msg_generic_error">Sorry, an error has occurred, please try again</string> <string name="msg_generic_error">Sorry, an error has occurred, please try again</string>
<string name="msg_no_data_to_display">No data to display</string> <string name="msg_no_data_to_display">No data to display</string>
<string name="msg_profile_update_successfully">Profile update successfully</string> <string name="msg_profile_update_successfully">Profile update successfully</string>
...@@ -88,6 +89,8 @@ ...@@ -88,6 +89,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>
...@@ -191,4 +194,9 @@ ...@@ -191,4 +194,9 @@
<string name="header_direct_messages">Direct Messages</string> <string name="header_direct_messages">Direct Messages</string>
<string name="header_live_chats">Live Chats</string> <string name="header_live_chats">Live Chats</string>
<string name="header_unknown">Unknown</string> <string name="header_unknown">Unknown</string>
<!--Notifications-->
<string name="notif_action_reply_hint">REPLY</string>
<string name="notif_error_sending">Reply has failed. Please try again.</string>
<string name="notif_success_sending">Message sent to %1$s!</string>
</resources> </resources>
\ No newline at end of file
...@@ -10,7 +10,7 @@ buildscript { ...@@ -10,7 +10,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.0' classpath 'com.android.tools.build:gradle:3.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.2.0' classpath 'com.google.gms:google-services:3.2.0'
......
...@@ -11,7 +11,7 @@ ext { ...@@ -11,7 +11,7 @@ ext {
// Main dependencies // Main dependencies
support : '27.1.0', support : '27.1.0',
constraintLayout : '1.0.2', constraintLayout : '1.0.2',
androidKtx : '0.2', androidKtx : '0.3',
dagger : '2.14.1', dagger : '2.14.1',
exoPlayer : '2.6.0', exoPlayer : '2.6.0',
playServices : '11.8.0', playServices : '11.8.0',
......
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