Unverified Commit f5f91c86 authored by divyanshu bhargava's avatar divyanshu bhargava Committed by GitHub

Merge pull request #1 from RocketChat/develop-2.x

merge
parents 2176c0ce ca864422
...@@ -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 2000 versionCode 2001
versionName "2.0.0-alpha1" versionName "2.0.0-beta1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
} }
...@@ -30,12 +30,16 @@ android { ...@@ -30,12 +30,16 @@ android {
buildTypes { buildTypes {
release { release {
buildConfigField "String", "REQUIRED_SERVER_VERSION", '"0.62.0"'
buildConfigField "String", "RECOMMENDED_SERVER_VERSION", '"0.63.0"'
signingConfig signingConfigs.release signingConfig signingConfigs.release
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
debug { debug {
buildConfigField "String", "REQUIRED_SERVER_VERSION", '"0.62.0"'
buildConfigField "String", "RECOMMENDED_SERVER_VERSION", '"0.63.0"'
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
} }
} }
...@@ -60,6 +64,7 @@ dependencies { ...@@ -60,6 +64,7 @@ dependencies {
implementation libraries.constraintLayout implementation libraries.constraintLayout
implementation libraries.cardView implementation libraries.cardView
implementation libraries.flexbox implementation libraries.flexbox
implementation libraries.customTabs
implementation libraries.androidKtx implementation libraries.androidKtx
...@@ -111,8 +116,6 @@ dependencies { ...@@ -111,8 +116,6 @@ dependencies {
androidTestImplementation(libraries.expressoCore, { androidTestImplementation(libraries.expressoCore, {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
implementation 'com.android.support:customtabs:27.0.2'
} }
kotlin { kotlin {
......
app/src/main/ic_launcher-web.png

20 KB | W: | H:

app/src/main/ic_launcher-web.png

39.1 KB | W: | H:

app/src/main/ic_launcher-web.png
app/src/main/ic_launcher-web.png
app/src/main/ic_launcher-web.png
app/src/main/ic_launcher-web.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -39,6 +39,7 @@ import kotlinx.coroutines.experimental.CommonPool ...@@ -39,6 +39,7 @@ import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.experimental.runBlocking
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference
import javax.inject.Inject import javax.inject.Inject
...@@ -86,6 +87,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -86,6 +87,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
// TODO - remove this on the future, temporary migration stuff for pre-release versions. // TODO - remove this on the future, temporary migration stuff for pre-release versions.
migrateInternalTokens() migrateInternalTokens()
context = WeakReference(applicationContext)
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
EmojiRepository.load(this) EmojiRepository.load(this)
...@@ -259,6 +261,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -259,6 +261,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> { override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> {
return broadcastReceiverInjector return broadcastReceiverInjector
} }
companion object {
var context: WeakReference<Context>? = null
fun getAppContext(): Context? {
return context?.get()
}
}
} }
private fun LocalRepository.setMigrated(migrated: Boolean) { private fun LocalRepository.setMigrated(migrated: Boolean) {
......
package chat.rocket.android.authentication.login.presentation package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.domain.model.TokenModel import chat.rocket.android.BuildConfig
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* 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.encodeToBase64 import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extensions.generateRandomString import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extensions.isEmail import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
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
...@@ -42,7 +42,8 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -42,7 +42,8 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
settingsInteractor: GetSettingsInteractor, settingsInteractor: GetSettingsInteractor,
serverInteractor: GetCurrentServerInteractor, serverInteractor: GetCurrentServerInteractor,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val factory: RocketChatClientFactory) { private val factory: RocketChatClientFactory)
: CheckServerPresenter(strategy, factory.create(serverInteractor.get()!!), view) {
// TODO - we should validate the current server when opening the app, and have a nonnull get() // TODO - we should validate the current server when opening the app, and have a nonnull get()
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
...@@ -57,6 +58,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -57,6 +58,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
setupUserRegistrationView() setupUserRegistrationView()
setupCasView() setupCasView()
setupOauthServicesView() setupOauthServicesView()
checkServerInfo()
} }
fun authenticateWithUserAndPassword(usernameOrEmail: String, password: String) { fun authenticateWithUserAndPassword(usernameOrEmail: String, password: String) {
...@@ -117,19 +119,19 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -117,19 +119,19 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
launchUI(strategy) { launchUI(strategy) {
try { try {
val services = client.settingsOauth().services val services = client.settingsOauth().services
val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
if (services.isNotEmpty()) { if (services.isNotEmpty()) {
val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
var totalSocialAccountsEnabled = 0 var totalSocialAccountsEnabled = 0
if (settings.isFacebookAuthenticationEnabled()) { if (settings.isFacebookAuthenticationEnabled()) {
view.enableLoginByFacebook() // //TODO: Remove until we have this implemented
totalSocialAccountsEnabled++ // view.enableLoginByFacebook()
// totalSocialAccountsEnabled++
} }
if (settings.isGithubAuthenticationEnabled()) { if (settings.isGithubAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GITHUB) val clientId = getOauthClientId(services, SERVICE_NAME_GITHUB)
if (clientId != null) { if (clientId != null) {
view.setupGithubButtonListener(UrlHelper.getGithubOauthUrl(clientId, state), state) view.setupGithubButtonListener(OauthHelper.getGithubOauthUrl(clientId, state), state)
view.enableLoginByGithub() view.enableLoginByGithub()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
...@@ -137,7 +139,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -137,7 +139,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
if (settings.isGoogleAuthenticationEnabled()) { if (settings.isGoogleAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GOOGLE) val clientId = getOauthClientId(services, SERVICE_NAME_GOOGLE)
if (clientId != null) { if (clientId != null) {
view.setupGoogleButtonListener(UrlHelper.getGoogleOauthUrl(clientId, currentServer, state), state) view.setupGoogleButtonListener(OauthHelper.getGoogleOauthUrl(clientId, currentServer, state), state)
view.enableLoginByGoogle() view.enableLoginByGoogle()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
...@@ -145,42 +147,44 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -145,42 +147,44 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
if (settings.isLinkedinAuthenticationEnabled()) { if (settings.isLinkedinAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_LINKEDIN) val clientId = getOauthClientId(services, SERVICE_NAME_LINKEDIN)
if (clientId != null) { if (clientId != null) {
view.setupGoogleButtonListener(UrlHelper.getLinkedinOauthUrl(clientId, currentServer, state), state) view.setupLinkedinButtonListener(OauthHelper.getLinkedinOauthUrl(clientId, currentServer, state), state)
view.enableLoginByLinkedin() view.enableLoginByLinkedin()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
if (settings.isMeteorAuthenticationEnabled()) { if (settings.isMeteorAuthenticationEnabled()) {
view.enableLoginByMeteor() //TODO: Remove until we have this implemented
totalSocialAccountsEnabled++ // view.enableLoginByMeteor()
// totalSocialAccountsEnabled++
} }
if (settings.isTwitterAuthenticationEnabled()) { if (settings.isTwitterAuthenticationEnabled()) {
view.enableLoginByTwitter() //TODO: Remove until we have this implemented
totalSocialAccountsEnabled++ // view.enableLoginByTwitter()
// totalSocialAccountsEnabled++
} }
if (settings.isGitlabAuthenticationEnabled()) { if (settings.isGitlabAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GILAB) val clientId = getOauthClientId(services, SERVICE_NAME_GILAB)
if (clientId != null) { if (clientId != null) {
view.setupGitlabButtonListener(UrlHelper.getGitlabOauthUrl(clientId, currentServer, state), state) view.setupGitlabButtonListener(OauthHelper.getGitlabOauthUrl(clientId, currentServer, state), state)
view.enableLoginByGitlab() view.enableLoginByGitlab()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
if (totalSocialAccountsEnabled > 0) { if (totalSocialAccountsEnabled > 0) {
view.showOauthView() view.enableOauthView()
if (totalSocialAccountsEnabled > 3) { if (totalSocialAccountsEnabled > 3) {
view.setupFabListener() view.setupFabListener()
} }
} else { } else {
view.hideOauthView() view.disableOauthView()
} }
} else { } else {
view.hideOauthView() view.disableOauthView()
} }
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
Timber.e(exception) Timber.e(exception)
view.hideOauthView() view.disableOauthView()
} }
} }
} }
...@@ -214,17 +218,28 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -214,17 +218,28 @@ 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 me = client.me() val username = client.me().username
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) if (username != null) {
saveAccount(me) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
saveToken(token) saveAccount(username)
registerPushToken() saveToken(token)
navigator.toChatList() registerPushToken()
navigator.toChatList()
} else if (loginType == TYPE_LOGIN_OAUTH) {
view.alertRequiresUsername()
}
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { when (exception) {
view.showMessage(it) is RocketChatTwoFactorException -> {
}.ifNull { navigator.toTwoFA(usernameOrEmail, password)
view.showGenericErrorMessage() }
else -> {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} }
} finally { } finally {
view.hideLoading() view.hideLoading()
...@@ -236,32 +251,32 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -236,32 +251,32 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
} }
} }
private fun saveToken(token: Token) {
tokenRepository.save(currentServer, token)
}
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it, getAccountsInteractor.get(), factory)
}
// TODO: When the push token is null, at some point we should receive it with
// onTokenRefresh() on FirebaseTokenService, we need to confirm it.
}
private fun getOauthClientId(listMap: List<Map<String, String>>, serviceName: String): String? { private fun getOauthClientId(listMap: List<Map<String, String>>, serviceName: String): String? {
return listMap.find { map -> map.containsValue(serviceName) } return listMap.find { map -> map.containsValue(serviceName) }
?.get("appId") ?.get("appId")
} }
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) UrlHelper.getServerLogoUrl(currentServer, it)
} }
val logo = settings.wideTile()?.let { val logo = settings.wideTile()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) UrlHelper.getServerLogoUrl(currentServer, it)
} }
val thumb = UrlHelper.getAvatarUrl(currentServer, me.username!!) val thumb = UrlHelper.getAvatarUrl(currentServer, username)
val account = Account(currentServer, icon, logo, me.username!!, thumb) val account = Account(currentServer, icon, logo, username, thumb)
saveAccountInteractor.save(account) saveAccountInteractor.save(account)
} }
private fun saveToken(token: Token) {
tokenRepository.save(currentServer, token)
}
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it, getAccountsInteractor.get(), factory)
}
// TODO: When the push token is null, at some point we should receive it with
// onTokenRefresh() on FirebaseTokenService, we need to confirm it.
}
} }
\ No newline at end of file
package chat.rocket.android.authentication.login.presentation package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.behaviours.InternetView 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 { interface LoginView : LoadingView, MessageView, InternetView, 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.
...@@ -72,18 +73,18 @@ interface LoginView : LoadingView, MessageView, InternetView { ...@@ -72,18 +73,18 @@ interface LoginView : LoadingView, MessageView, InternetView {
fun hideSignUpView() fun hideSignUpView()
/** /**
* Shows the oauth view if the login via social accounts is enabled by the server settings. * Enables and shows the oauth view if there is login via social accounts enabled by the server settings.
* *
* REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle], * REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter] or [enableLoginByGitlab]) for the oauth view. * [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter] or [enableLoginByGitlab]) for the oauth view.
* If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s). * If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s).
*/ */
fun showOauthView() fun enableOauthView()
/** /**
* Hides the oauth view. * Disables and hides the Oauth view if there is not login via social accounts enabled by the server settings.
*/ */
fun hideOauthView() fun disableOauthView()
/** /**
* Shows the login button. * Shows the login button.
...@@ -186,4 +187,9 @@ interface LoginView : LoadingView, MessageView, InternetView { ...@@ -186,4 +187,9 @@ interface LoginView : LoadingView, MessageView, InternetView {
* Alerts the user about a wrong inputted password. * Alerts the user about a wrong inputted password.
*/ */
fun alertWrongPassword() fun alertWrongPassword()
/**
* Alerts the user about the need of creating an username using the web app when creating an user through OAuth.
*/
fun alertRequiresUsername()
} }
\ No newline at end of file
...@@ -2,6 +2,7 @@ package chat.rocket.android.authentication.login.ui ...@@ -2,6 +2,7 @@ package chat.rocket.android.authentication.login.ui
import DrawableHelper import DrawableHelper
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
...@@ -13,6 +14,8 @@ import android.view.ViewGroup ...@@ -13,6 +14,8 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.Toast
import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.login.presentation.LoginPresenter import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView import chat.rocket.android.authentication.login.presentation.LoginView
...@@ -33,6 +36,7 @@ internal const val REQUEST_CODE_FOR_OAUTH = 2 ...@@ -33,6 +36,7 @@ internal const val REQUEST_CODE_FOR_OAUTH = 2
class LoginFragment : Fragment(), LoginView { class LoginFragment : Fragment(), LoginView {
@Inject lateinit var presenter: LoginPresenter @Inject lateinit var presenter: LoginPresenter
private var isOauthViewEnable = false
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
areLoginOptionsNeeded() areLoginOptionsNeeded()
} }
...@@ -182,14 +186,15 @@ class LoginFragment : Fragment(), LoginView { ...@@ -182,14 +186,15 @@ class LoginFragment : Fragment(), LoginView {
text_new_to_rocket_chat.setVisible(false) text_new_to_rocket_chat.setVisible(false)
} }
override fun showOauthView() { override fun enableOauthView() {
isOauthViewEnable = true
showThreeSocialAccountsMethods() showThreeSocialAccountsMethods()
social_accounts_container.setVisible(true) social_accounts_container.setVisible(true)
} }
override fun hideOauthView() { override fun disableOauthView() {
isOauthViewEnable = false
social_accounts_container.setVisible(false) social_accounts_container.setVisible(false)
button_fab.setVisible(false)
} }
override fun showLoginButton() { override fun showLoginButton() {
...@@ -287,6 +292,31 @@ class LoginFragment : Fragment(), LoginView { ...@@ -287,6 +292,31 @@ class LoginFragment : Fragment(), LoginView {
text_password.requestFocus() text_password.requestFocus()
} }
override fun alertRequiresUsername() {
showToast(getString(R.string.msg_requires_username), Toast.LENGTH_LONG)
}
override fun alertNotRecommendedVersion() {
context?.let {
AlertDialog.Builder(it)
.setMessage(getString(R.string.msg_ver_not_recommended, BuildConfig.RECOMMENDED_SERVER_VERSION))
.setPositiveButton(R.string.msg_ok, null)
.create()
.show()
}
}
override fun blockAndAlertNotRequiredVersion() {
context?.let {
AlertDialog.Builder(it)
.setMessage(getString(R.string.msg_ver_not_minimum, BuildConfig.REQUIRED_SERVER_VERSION))
.setOnDismissListener { activity?.onBackPressed() }
.setPositiveButton(R.string.msg_ok, null)
.create()
.show()
}
}
private fun showRemainingSocialAccountsView() { private fun showRemainingSocialAccountsView() {
social_accounts_container.postDelayed({ social_accounts_container.postDelayed({
(0..social_accounts_container.childCount) (0..social_accounts_container.childCount)
...@@ -328,4 +358,17 @@ class LoginFragment : Fragment(), LoginView { ...@@ -328,4 +358,17 @@ class LoginFragment : Fragment(), LoginView {
.take(3) .take(3)
.forEach { it.setVisible(true) } .forEach { it.setVisible(true) }
} }
fun showOauthView() {
if (isOauthViewEnable) {
social_accounts_container.setVisible(true)
}
}
fun hideOauthView() {
if (isOauthViewEnable) {
social_accounts_container.setVisible(false)
button_fab.setVisible(false)
}
}
} }
\ No newline at end of file
package chat.rocket.android.authentication.server.presentation
interface VersionCheckView {
/**
* Alerts the user about the server version not meeting the recommended server version.
*/
fun alertNotRecommendedVersion()
/**
* Block user to proceed and alert him due to server having an unsupported server version.
*/
fun blockAndAlertNotRequiredVersion()
}
\ No newline at end of file
...@@ -102,31 +102,39 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -102,31 +102,39 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
return fragmentDispatchingAndroidInjector return fragmentDispatchingAndroidInjector
} }
fun showRoomTypeIcon(showRoomTypeIcon: Boolean) {
if (showRoomTypeIcon) {
val roomType = roomTypeOf(chatRoomType)
val drawable = when (roomType) {
is RoomType.Channel -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_channel, this)
}
is RoomType.PrivateGroup -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_lock, this)
}
is RoomType.DirectMessage -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_dm, this)
}
else -> null
}
drawable?.let {
val wrappedDrawable = DrawableHelper.wrapDrawable(it)
val mutableDrawable = wrappedDrawable.mutate()
DrawableHelper.tintDrawable(mutableDrawable, this, R.color.white)
DrawableHelper.compoundDrawable(text_room_name, mutableDrawable)
}
} else {
text_room_name.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
}
}
private fun setupToolbar() { private fun setupToolbar() {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false) supportActionBar?.setDisplayShowTitleEnabled(false)
text_room_name.textContent = chatRoomName text_room_name.textContent = chatRoomName
val roomType = roomTypeOf(chatRoomType) showRoomTypeIcon(true)
val drawable = when (roomType) {
is RoomType.Channel -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_channel, this)
}
is RoomType.PrivateGroup -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_lock, this)
}
is RoomType.DirectMessage -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_dm, this)
}
else -> null
}
drawable?.let {
val wrappedDrawable = DrawableHelper.wrapDrawable(it)
val mutableDrawable = wrappedDrawable.mutate()
DrawableHelper.tintDrawable(mutableDrawable, this, R.color.white)
DrawableHelper.compoundDrawable(text_room_name, mutableDrawable)
}
toolbar.setNavigationOnClickListener { toolbar.setNavigationOnClickListener {
finishActivity() finishActivity()
......
...@@ -123,6 +123,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -123,6 +123,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
setupMessageComposer() setupMessageComposer()
setupSuggestionsView() setupSuggestionsView()
setupActionSnackbar() setupActionSnackbar()
activity?.apply {
(this as? ChatRoomActivity)?.showRoomTypeIcon(true)
}
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
......
...@@ -7,8 +7,8 @@ data class MessageAttachmentViewModel( ...@@ -7,8 +7,8 @@ data class MessageAttachmentViewModel(
override val message: Message, override val message: Message,
override val rawData: Message, override val rawData: Message,
override val messageId: String, override val messageId: String,
var senderName: String, var senderName: String?,
val time: CharSequence, val time: CharSequence?,
val content: CharSequence, val content: CharSequence,
val isPinned: Boolean, val isPinned: Boolean,
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
......
...@@ -103,8 +103,8 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -103,8 +103,8 @@ class ViewModelMapper @Inject constructor(private val context: Context,
} }
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 = getTime(attachment.timestamp!!) val time = attachment.timestamp?.let { getTime(it) }
val attachmentText = when (attachment.attachments.orEmpty().firstOrNull()) { val attachmentText = when (attachment.attachments.orEmpty().firstOrNull()) {
is ImageAttachment -> context.getString(R.string.msg_preview_photo) is ImageAttachment -> context.getString(R.string.msg_preview_photo)
is VideoAttachment -> context.getString(R.string.msg_preview_video) is VideoAttachment -> context.getString(R.string.msg_preview_video)
......
...@@ -2,6 +2,9 @@ package chat.rocket.android.chatrooms.presentation ...@@ -2,6 +2,9 @@ package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.chatroom.viewmodel.ViewModelMapper import chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.ChatRoomsSortOrder
import chat.rocket.android.helper.Constants
import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManager import chat.rocket.android.server.infraestructure.ConnectionManager
...@@ -26,6 +29,7 @@ import kotlinx.coroutines.experimental.android.UI ...@@ -26,6 +29,7 @@ import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.KProperty1
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
...@@ -164,9 +168,53 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -164,9 +168,53 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
return getChatRoomsWithPreviews(sortedRooms) return getChatRoomsWithPreviews(sortedRooms)
} }
fun updateSortedChatRooms() {
val currentServer = serverInteractor.get()!!
launchUI(strategy) {
val roomList = getChatRoomsInteractor.get(currentServer)
view.updateChatRooms(sortRooms(roomList))
}
}
private fun sortRooms(chatRooms: List<ChatRoom>): List<ChatRoom> { private fun sortRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY)
val groupByType = SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)
val openChatRooms = getOpenChatRooms(chatRooms) val openChatRooms = getOpenChatRooms(chatRooms)
return sortChatRooms(openChatRooms)
return when (sortType) {
ChatRoomsSortOrder.ALPHABETICAL -> {
when (groupByType) {
true -> openChatRooms.sortedWith(compareBy(ChatRoom::type).thenBy { it.name })
false -> openChatRooms.sortedWith(compareBy(ChatRoom::name))
}
}
ChatRoomsSortOrder.ACTIVITY -> {
when (groupByType) {
true -> openChatRooms.sortedWith(compareBy(ChatRoom::type).thenByDescending { it.lastMessage?.timestamp })
false -> openChatRooms.sortedByDescending { chatRoom ->
chatRoom.lastMessage?.timestamp
}
}
}
else -> {
openChatRooms
}
}
}
private fun compareBy(selector: KProperty1<ChatRoom, RoomType>): Comparator<ChatRoom> {
return Comparator { a, b -> getTypeConstant(a.type) - getTypeConstant(b.type) }
}
private fun getTypeConstant(roomType: RoomType): Int {
return when (roomType) {
is RoomType.Channel -> Constants.CHATROOM_CHANNEL
is RoomType.PrivateGroup -> Constants.CHATROOM_PRIVATE_GROUP
is RoomType.DirectMessage -> Constants.CHATROOM_DM
is RoomType.Livechat -> Constants.CHATROOM_LIVE_CHAT
else -> 0
}
} }
private fun updateRooms() { private fun updateRooms() {
...@@ -291,26 +339,27 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -291,26 +339,27 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList() val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == room.id } val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == room.id }
chatRoom?.apply { chatRoom?.apply {
val newRoom = ChatRoom(room.id, val newRoom = ChatRoom(id = room.id,
room.type, type = room.type,
room.user ?: user, user = room.user ?: user,
room.name ?: name, name = room.name ?: name,
room.fullName ?: fullName, fullName = room.fullName ?: fullName,
room.readonly, readonly = room.readonly,
room.updatedAt ?: updatedAt, updatedAt = room.updatedAt ?: updatedAt,
timestamp, timestamp = timestamp,
lastSeen, lastSeen = lastSeen,
room.topic, topic = room.topic,
room.description, description = room.description,
room.announcement, announcement = room.announcement,
default, default = default,
open, favorite = favorite,
alert, open = open,
unread, alert = alert,
userMenstions, unread = unread,
groupMentions, userMenstions = userMenstions,
room.lastMessage, groupMentions = groupMentions,
client) lastMessage = room.lastMessage,
client = client)
removeRoom(room.id, chatRooms) removeRoom(room.id, chatRooms)
chatRooms.add(newRoom) chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms)) saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
...@@ -323,26 +372,27 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -323,26 +372,27 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList() val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == subscription.roomId } val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == subscription.roomId }
chatRoom?.apply { chatRoom?.apply {
val newRoom = ChatRoom(subscription.roomId, val newRoom = ChatRoom(id = subscription.roomId,
subscription.type, type = subscription.type,
subscription.user ?: user, user = subscription.user ?: user,
subscription.name, name = subscription.name,
subscription.fullName ?: fullName, fullName = subscription.fullName ?: fullName,
subscription.readonly ?: readonly, readonly = subscription.readonly ?: readonly,
subscription.updatedAt ?: updatedAt, updatedAt = subscription.updatedAt ?: updatedAt,
subscription.timestamp ?: timestamp, timestamp = subscription.timestamp ?: timestamp,
subscription.lastSeen ?: lastSeen, lastSeen = subscription.lastSeen ?: lastSeen,
topic, topic = topic,
description, description = description,
announcement, announcement = announcement,
subscription.isDefault, default = subscription.isDefault,
subscription.open, favorite = subscription.isFavorite,
subscription.alert, open = subscription.open,
subscription.unread, alert = subscription.alert,
subscription.userMentions, unread = subscription.unread,
subscription.groupMentions, userMenstions = subscription.userMentions,
lastMessage, groupMentions = subscription.groupMentions,
client) lastMessage = lastMessage,
client = client)
removeRoom(subscription.roomId, chatRooms) removeRoom(subscription.roomId, chatRooms)
chatRooms.add(newRoom) chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms)) saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
......
package chat.rocket.android.chatrooms.ui package chat.rocket.android.chatrooms.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
...@@ -9,14 +12,20 @@ import android.support.v7.widget.DefaultItemAnimator ...@@ -9,14 +12,20 @@ import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import android.widget.CheckBox
import android.widget.RadioGroup
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.helper.ChatRoomsSortOrder
import chat.rocket.android.helper.Constants
import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import chat.rocket.android.widget.DividerItemDecoration import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.common.model.RoomType
import chat.rocket.core.internal.realtime.State import chat.rocket.core.internal.realtime.State
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
...@@ -28,15 +37,18 @@ import kotlinx.coroutines.experimental.async ...@@ -28,15 +37,18 @@ import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import javax.inject.Inject import javax.inject.Inject
class ChatRoomsFragment : Fragment(), ChatRoomsView { class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject lateinit var presenter: ChatRoomsPresenter @Inject lateinit var presenter: ChatRoomsPresenter
@Inject lateinit var serverInteractor: GetCurrentServerInteractor @Inject lateinit var serverInteractor: GetCurrentServerInteractor
@Inject lateinit var settingsRepository: SettingsRepository @Inject lateinit var settingsRepository: SettingsRepository
@Inject lateinit var localRepository: LocalRepository @Inject lateinit var localRepository: LocalRepository
private lateinit var preferences: SharedPreferences
private var searchView: SearchView? = null private var searchView: SearchView? = null
private val handler = Handler() private val handler = Handler()
private var listJob: Job? = null private var listJob: Job? = null
private var sectionedAdapter: SimpleSectionedRecyclerViewAdapter? = null
companion object { companion object {
fun newInstance() = ChatRoomsFragment() fun newInstance() = ChatRoomsFragment()
...@@ -46,6 +58,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -46,6 +58,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
setHasOptionsMenu(true) setHasOptionsMenu(true)
preferences = context?.getSharedPreferences("temp", Context.MODE_PRIVATE)!!
} }
override fun onDestroy() { override fun onDestroy() {
...@@ -87,20 +100,76 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -87,20 +100,76 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}) })
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.action_sort -> {
val dialogLayout = layoutInflater.inflate(R.layout.chatroom_sort_dialog, null)
val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY)
val groupByType = SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)
val radioGroup = dialogLayout.findViewById<RadioGroup>(R.id.radio_group_sort)
val groupByTypeCheckBox = dialogLayout.findViewById<CheckBox>(R.id.checkbox_group_by_type)
radioGroup.check(when (sortType) {
0 -> R.id.radio_sort_alphabetical
else -> R.id.radio_sort_activity
})
radioGroup.setOnCheckedChangeListener({ _, checkedId ->
run {
SharedPreferenceHelper.putInt(Constants.CHATROOM_SORT_TYPE_KEY, when (checkedId) {
R.id.radio_sort_alphabetical -> 0
R.id.radio_sort_activity -> 1
else -> 1
})
presenter.updateSortedChatRooms()
invalidateQueryOnSearch()
}
})
groupByTypeCheckBox.isChecked = groupByType
groupByTypeCheckBox.setOnCheckedChangeListener({ _, isChecked ->
SharedPreferenceHelper.putBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, isChecked)
presenter.updateSortedChatRooms()
invalidateQueryOnSearch()
})
val dialogSort = AlertDialog.Builder(context)
.setTitle(R.string.dialog_sort_title)
.setView(dialogLayout)
.setPositiveButton("Done", { dialog, _ -> dialog.dismiss() })
dialogSort.show()
}
}
return super.onOptionsItemSelected(item)
}
private fun invalidateQueryOnSearch(){
searchView?.let {
if (!searchView!!.isIconified){
queryChatRoomsByName(searchView!!.query.toString())
}
}
}
override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) { override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) {
activity?.apply { activity?.apply {
listJob?.cancel() listJob?.cancel()
listJob = launch(UI) { listJob = launch(UI) {
val adapter = recycler_view.adapter as ChatRoomsAdapter 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.dev/issues/5a90d4718cb3c2fa63b3f557?time=last-seven-days
// TODO - fix this bug to reenable DiffUtil // TODO - fix this bug to reenable DiffUtil
val diff = async(CommonPool) { val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.dataSet, newDataSet)) DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await() }.await()
if (isActive) { if (isActive) {
adapter.updateRooms(newDataSet) adapter.baseAdapter.updateRooms(newDataSet)
diff.dispatchUpdatesTo(adapter) diff.dispatchUpdatesTo(adapter)
//Set sections always after data set is updated
setSections()
} }
} }
} }
...@@ -162,12 +231,45 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -162,12 +231,45 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end))) resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end)))
recycler_view.itemAnimator = DefaultItemAnimator() recycler_view.itemAnimator = DefaultItemAnimator()
// TODO - use a ViewModel Mapper instead of using settings on the adapter // TODO - use a ViewModel Mapper instead of using settings on the adapter
recycler_view.adapter = ChatRoomsAdapter(
this, val baseAdapter = ChatRoomsAdapter(this,
settingsRepository.get(serverInteractor.get()!!), localRepository) { chatRoom -> settingsRepository.get(serverInteractor.get()!!), localRepository) { chatRoom -> presenter.loadChatRoom(chatRoom) }
presenter.loadChatRoom(chatRoom)
sectionedAdapter = SimpleSectionedRecyclerViewAdapter(this, R.layout.item_chatroom_header, R.id.text_chatroom_header, baseAdapter!!)
recycler_view.adapter = sectionedAdapter
}
}
private fun setSections() {
//Don't add section if not grouping by RoomType
if (!SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)) {
sectionedAdapter?.clearSections()
return
}
val sections = ArrayList<SimpleSectionedRecyclerViewAdapter.Section>()
sectionedAdapter?.baseAdapter?.dataSet?.let {
var previousChatRoomType = ""
for ((position, chatRoom) in it.withIndex()) {
val type = chatRoom.type.toString()
if (type != previousChatRoomType) {
val title = when (type) {
RoomType.CHANNEL.toString() -> resources.getString(R.string.header_channel)
RoomType.PRIVATE_GROUP.toString() -> resources.getString(R.string.header_private_groups)
RoomType.DIRECT_MESSAGE.toString() -> resources.getString(R.string.header_direct_messages)
RoomType.LIVECHAT.toString() -> resources.getString(R.string.header_live_chats)
else -> resources.getString(R.string.header_unknown)
}
sections.add(SimpleSectionedRecyclerViewAdapter.Section(position, title))
}
previousChatRoomType = chatRoom.type.toString()
} }
} }
val dummy = arrayOfNulls<SimpleSectionedRecyclerViewAdapter.Section>(sections.size)
sectionedAdapter?.setSections(sections.toArray(dummy))
} }
private fun queryChatRoomsByName(name: String?): Boolean { private fun queryChatRoomsByName(name: String?): Boolean {
......
package chat.rocket.android.chatrooms.ui
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import java.util.*
class SimpleSectionedRecyclerViewAdapter(private val context: Context, private val sectionResourceId: Int, private val textResourceId: Int,
val baseAdapter: ChatRoomsAdapter) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var isValid = true
private val sectionsHeaders = SparseArray<Section>()
init {
baseAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
isValid = baseAdapter.itemCount > 0
notifyDataSetChanged()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
isValid = baseAdapter.itemCount > 0
notifyItemRangeChanged(positionStart, itemCount)
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
isValid = baseAdapter.itemCount > 0
notifyItemRangeInserted(positionStart, itemCount)
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
isValid = baseAdapter.itemCount > 0
notifyItemRangeRemoved(positionStart, itemCount)
}
})
}
class SectionViewHolder(view: View, textResourceId: Int) : RecyclerView.ViewHolder(view) {
var title: TextView = view.findViewById<View>(textResourceId) as TextView
}
override fun onCreateViewHolder(parent: ViewGroup, typeView: Int): RecyclerView.ViewHolder {
return if (typeView == SECTION_TYPE) {
val view = LayoutInflater.from(context).inflate(sectionResourceId, parent, false)
SectionViewHolder(view, textResourceId)
} else {
baseAdapter.onCreateViewHolder(parent, typeView - 1)
}
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
if (isSectionHeaderPosition(position)) {
(viewHolder as SectionViewHolder).title.text = sectionsHeaders.get(position).title
} else {
baseAdapter.onBindViewHolder(viewHolder as ChatRoomsAdapter.ViewHolder, sectionedPositionToPosition(position))
}
}
override fun getItemViewType(position: Int): Int {
return if (isSectionHeaderPosition(position))
SECTION_TYPE
else
baseAdapter.getItemViewType(sectionedPositionToPosition(position)) + 1
}
class Section(internal var firstPosition: Int, var title: CharSequence) {
internal var sectionedPosition: Int = 0
}
fun setSections(sections: Array<Section>) {
sectionsHeaders.clear()
Arrays.sort(sections) { section1, section2 ->
when {
section1.firstPosition == section2.firstPosition -> 0
section1.firstPosition < section2.firstPosition -> -1
else -> 1
}
}
for ((offset, section) in sections.withIndex()) {
section.sectionedPosition = section.firstPosition + offset
sectionsHeaders.append(section.sectionedPosition, section)
}
notifyDataSetChanged()
}
fun clearSections(){
sectionsHeaders.clear()
notifyDataSetChanged()
}
fun positionToSectionedPosition(position: Int): Int {
var offset = 0
for (i in 0 until sectionsHeaders.size()) {
if (sectionsHeaders.valueAt(i).firstPosition > position) {
break
}
++offset
}
return position + offset
}
private fun sectionedPositionToPosition(sectionedPosition: Int): Int {
if (isSectionHeaderPosition(sectionedPosition)) {
return RecyclerView.NO_POSITION
}
var offset = 0
for (i in 0 until sectionsHeaders.size()) {
if (sectionsHeaders.valueAt(i).sectionedPosition > sectionedPosition) {
break
}
--offset
}
return sectionedPosition + offset
}
private fun isSectionHeaderPosition(position: Int): Boolean {
return sectionsHeaders.get(position) != null
}
override fun getItemId(position: Int): Long {
return when (isSectionHeaderPosition(position)) {
true -> (Integer.MAX_VALUE - sectionsHeaders.indexOfKey(position)).toLong()
false -> baseAdapter.getItemId(sectionedPositionToPosition(position))
}
}
override fun getItemCount(): Int {
return if (isValid) baseAdapter.itemCount + sectionsHeaders.size() else 0
}
companion object {
private const val SECTION_TYPE = 0
}
}
package chat.rocket.android.helper
object Constants {
const val CHATROOM_SORT_TYPE_KEY: String = "chatroom_sort_type"
const val CHATROOM_GROUP_BY_TYPE_KEY: String = "chatroom_group_by_type"
const val CHATROOM_GROUP_FAVOURITES_KEY: String = "chatroom_group_favourites"
//Used to sort chat rooms
const val CHATROOM_CHANNEL = 0
const val CHATROOM_PRIVATE_GROUP = 1
const val CHATROOM_DM = 2
const val CHATROOM_LIVE_CHAT = 3
}
object ChatRoomsSortOrder {
const val ALPHABETICAL: Int = 0
const val ACTIVITY: Int = 1
}
\ No newline at end of file
package chat.rocket.android.helper
object OauthHelper {
/**
* Returns the Github Oauth URL.
*
* @param clientId The GitHub client ID.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Github Oauth URL.
*/
fun getGithubOauthUrl(clientId: String, state: String): String {
return "https://github.com/login/oauth/authorize" +
"?client_id=$clientId" +
"&state=$state" +
"&scope=user:email"
}
/**
* Returns the Google Oauth URL.
*
* @param clientId The Google client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Google Oauth URL.
*/
fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://accounts.google.com/o/oauth2/v2/auth" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/google?close" +
"&state=$state" +
"&response_type=code" +
"&scope=email%20profile"
}
/**
* Returns the Linkedin Oauth URL.
*
* @param clientId The Linkedin client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Linkedin Oauth URL.
*/
fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://linkedin.com/oauth/v2/authorization" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/linkedin?close" +
"&state=$state" +
"&response_type=code"
}
/**
* Returns the Gitlab Oauth URL.
*
* @param clientId The Gitlab client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Gitlab Oauth URL.
*/
fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://gitlab.com/oauth/authorize" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/gitlab?close" +
"&state=$state" +
"&response_type=code" +
"&scope=read_user"
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.content.SharedPreferences
import android.preference.PreferenceManager
import chat.rocket.android.app.RocketChatApplication
object SharedPreferenceHelper {
private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(RocketChatApplication.getAppContext())
private var editor: SharedPreferences.Editor? = sharedPreferences.edit()
//Add more methods for other types if needed
fun putInt(key: String, value: Int) {
editor!!.putInt(key, value).apply()
}
fun getInt(key: String, defaultValue: Int): Int {
return sharedPreferences.getInt(key, defaultValue)
}
fun putLong(key: String, value: Long) {
editor!!.putLong(key, value).apply()
}
fun getLong(key: String, defaultValue: Long): Long {
return sharedPreferences.getLong(key, defaultValue)
}
fun putString(key: String, value: String) {
editor!!.putString(key, value).apply()
}
fun getString(key: String, defaultValue: String): String? {
return sharedPreferences.getString(key, defaultValue)
}
fun putBoolean(key: String, value: Boolean) {
editor!!.putBoolean(key, value).apply()
}
fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return sharedPreferences.getBoolean(key, defaultValue)
}
fun remove(key: String) {
editor!!.remove(key).apply()
}
}
\ No newline at end of file
...@@ -22,7 +22,7 @@ object UrlHelper { ...@@ -22,7 +22,7 @@ object UrlHelper {
* @return The server logo URL. * @return The server logo URL.
*/ */
fun getServerLogoUrl(serverUrl: String, favicon: String): String = fun getServerLogoUrl(serverUrl: String, favicon: String): String =
removeTrailingSlash(serverUrl) + "/$favicon" removeTrailingSlash(serverUrl) + "/$favicon"
/** /**
* Returns the CAS URL. * Returns the CAS URL.
...@@ -35,49 +35,6 @@ object UrlHelper { ...@@ -35,49 +35,6 @@ object UrlHelper {
fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String = fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String =
removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token
/**
* Returns the Github Oauth URL.
*
* @param clientId The GitHub client ID.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Github Oauth URL.
*/
fun getGithubOauthUrl(clientId: String, state: String): String =
"https://github.com/login/oauth/authorize?scope=user:email&client_id=$clientId&state=$state"
/**
* Returns the Google Oauth URL.
*
* @param clientId The Google client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Google Oauth URL.
*/
fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String) =
"https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/google?close&response_type=code&state=$state&scope=email%20profile"
/**
* Returns the Linkedin Oauth URL.
*
* @param clientId The Linkedin client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Linkedin Oauth URL.
*/
fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String) =
"https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/linkedin?close&state=$state"
/**
* Returns the Gitlab Oauth URL.
*
* @param clientId The Gitlab client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Gitlab Oauth URL.
*/
fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String =
"https://gitlab.com/oauth/authorize?client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/gitlab?close&response_type=code&state=$state&scope=read_user"
/** /**
* Returns the server's Terms of Service URL. * Returns the server's Terms of Service URL.
* *
......
...@@ -9,33 +9,34 @@ import chat.rocket.android.server.domain.* ...@@ -9,33 +9,34 @@ 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.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.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.common.RocketChatAuthException
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.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.registerPushToken
import chat.rocket.core.internal.rest.unregisterPushToken import chat.rocket.core.internal.rest.unregisterPushToken
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MainPresenter @Inject constructor( class MainPresenter @Inject constructor(
private val view: MainView, private val view: MainView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: MainNavigator, private val navigator: MainNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val navHeaderMapper: NavHeaderViewModelMapper, private val navHeaderMapper: NavHeaderViewModelMapper,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
private val removeAccountInterector: RemoveAccountInterector, private val removeAccountInterector: RemoveAccountInterector,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
getSettingsInteractor: GetSettingsInteractor, getSettingsInteractor: GetSettingsInteractor,
managerFactory: ConnectionManagerFactory managerFactory: ConnectionManagerFactory
) { ) : CheckServerPresenter(strategy, client = factory.create(serverInteractor.get()!!), view = view) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val manager = managerFactory.create(currentServer) private val manager = managerFactory.create(currentServer)
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
...@@ -48,18 +49,27 @@ class MainPresenter @Inject constructor( ...@@ -48,18 +49,27 @@ class MainPresenter @Inject constructor(
fun toSettings() = navigator.toSettings() fun toSettings() = navigator.toSettings()
fun loadCurrentInfo() { fun loadCurrentInfo() {
checkServerInfo()
launchUI(strategy) { launchUI(strategy) {
try { try {
val me = client.me() val 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) {
Timber.d(ex, "Error loading my information for navheader") when (ex) {
ex.message?.let { is RocketChatAuthException -> {
view.showMessage(it) logout()
}.ifNull { }
view.showGenericErrorMessage() else -> {
Timber.d(ex, "Error loading my information for navheader")
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} }
} }
} }
...@@ -81,17 +91,25 @@ class MainPresenter @Inject constructor( ...@@ -81,17 +91,25 @@ class MainPresenter @Inject constructor(
try { try {
clearTokens() clearTokens()
client.logout() client.logout()
disconnect()
removeAccountInterector.remove(currentServer)
tokenRepository.remove(currentServer)
navigator.toNewServer()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
Timber.d(exception, "Error calling logout")
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} }
try {
disconnect()
removeAccountInterector.remove(currentServer)
tokenRepository.remove(currentServer)
navigator.toNewServer()
} catch (ex: Exception) {
Timber.d(ex, "Error cleaning up the session...")
}
navigator.toNewServer()
} }
} }
...@@ -100,7 +118,6 @@ class MainPresenter @Inject constructor( ...@@ -100,7 +118,6 @@ class MainPresenter @Inject constructor(
val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN) val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN)
if (pushToken != null) { if (pushToken != null) {
client.unregisterPushToken(pushToken) client.unregisterPushToken(pushToken)
localRepository.clear(LocalRepository.KEY_PUSH_TOKEN)
} }
localRepository.clearAllFromServer(currentServer) localRepository.clearAllFromServer(currentServer)
} }
......
package chat.rocket.android.main.presentation package chat.rocket.android.main.presentation
import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.android.main.viewmodel.NavHeaderViewModel import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
interface MainView : MessageView { interface MainView : MessageView, VersionCheckView {
fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>) fun setupNavHeader(model: NavHeaderViewModel, accounts: List<Account>)
fun closeServerSelection() fun closeServerSelection()
} }
\ No newline at end of file
package chat.rocket.android.main.ui package chat.rocket.android.main.ui
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
...@@ -8,6 +9,7 @@ import android.support.v7.widget.LinearLayoutManager ...@@ -8,6 +9,7 @@ import android.support.v7.widget.LinearLayoutManager
import android.view.Gravity import android.view.Gravity
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.main.adapter.AccountSelector import chat.rocket.android.main.adapter.AccountSelector
import chat.rocket.android.main.adapter.AccountsAdapter import chat.rocket.android.main.adapter.AccountsAdapter
...@@ -39,6 +41,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -39,6 +41,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment> @Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject lateinit var presenter: MainPresenter @Inject lateinit var presenter: MainPresenter
private var isFragmentAdded: Boolean = false private var isFragmentAdded: Boolean = false
private var expanded = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this) AndroidInjection.inject(this)
...@@ -86,7 +89,22 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -86,7 +89,22 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
view_navigation.getHeaderView(0).account_container.performClick() view_navigation.getHeaderView(0).account_container.performClick()
} }
private var expanded = false override fun alertNotRecommendedVersion() {
AlertDialog.Builder(this)
.setMessage(getString(R.string.msg_ver_not_recommended, BuildConfig.RECOMMENDED_SERVER_VERSION))
.setPositiveButton(R.string.msg_ok, null)
.create()
.show()
}
override fun blockAndAlertNotRequiredVersion() {
AlertDialog.Builder(this)
.setMessage(getString(R.string.msg_ver_not_minimum, BuildConfig.REQUIRED_SERVER_VERSION))
.setOnDismissListener { presenter.logout() }
.setPositiveButton(R.string.msg_ok, null)
.create()
.show()
}
private fun setupAccountsList(header: View, accounts: List<Account>) { private fun setupAccountsList(header: View, accounts: List<Account>) {
accounts_list.layoutManager = LinearLayoutManager(this) accounts_list.layoutManager = LinearLayoutManager(this)
...@@ -111,6 +129,12 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp ...@@ -111,6 +129,12 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
expanded = !expanded expanded = !expanded
} }
header.image_avatar.setOnClickListener {
view_navigation.menu.findItem(R.id.action_profile).isChecked = true
presenter.toUserProfile()
drawer_layout.closeDrawer(Gravity.START)
}
} }
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) = showToast(resId)
......
...@@ -7,16 +7,16 @@ import javax.inject.Inject ...@@ -7,16 +7,16 @@ import javax.inject.Inject
class NavHeaderViewModelMapper @Inject constructor(serverInteractor: GetCurrentServerInteractor, class NavHeaderViewModelMapper @Inject constructor(serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor) { getSettingsInteractor: GetSettingsInteractor) {
private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!) private val currentServer = serverInteractor.get()!!
private val baseUrl = settings.baseUrl()!! private var settings: PublicSettings = getSettingsInteractor.get(currentServer)
fun mapToViewModel(me: Myself): NavHeaderViewModel { fun mapToViewModel(me: Myself): NavHeaderViewModel {
val username = mapUsername(me) val username = mapUsername(me)
val thumb = me.username?.let { UrlHelper.getAvatarUrl(baseUrl, it) } val thumb = me.username?.let { UrlHelper.getAvatarUrl(currentServer, it) }
val image = settings.wideTile() ?: settings.faviconLarge() val image = settings.wideTile() ?: settings.faviconLarge()
val logo = image?.let { UrlHelper.getServerLogoUrl(baseUrl, it) } val logo = image?.let { UrlHelper.getServerLogoUrl(currentServer, it) }
return NavHeaderViewModel(username, baseUrl, thumb, logo) return NavHeaderViewModel(username, currentServer, thumb, logo)
} }
private fun mapUsername(me: Myself): String { private fun mapUsername(me: Myself): String {
......
...@@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity ...@@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
...@@ -83,7 +84,18 @@ class MembersFragment : Fragment(), MembersView { ...@@ -83,7 +84,18 @@ class MembersFragment : Fragment(), MembersView {
} else { } else {
adapter.appendData(dataSet) adapter.appendData(dataSet)
} }
if (this is ChatRoomActivity) {
this.showRoomTypeIcon(false)
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
(activity as ChatRoomActivity).showRoomTypeIcon(true)
return super.onOptionsItemSelected(item)
} }
return super.onOptionsItemSelected(item)
} }
override fun showLoading() = view_loading.setVisible(true) override fun showLoading() = view_loading.setVisible(true)
......
...@@ -95,5 +95,5 @@ fun PublicSettings.uploadMaxFileSize(): Int { ...@@ -95,5 +95,5 @@ fun PublicSettings.uploadMaxFileSize(): Int {
return this[UPLOAD_MAX_FILE_SIZE]?.value?.let { it as Int } ?: Int.MAX_VALUE return this[UPLOAD_MAX_FILE_SIZE]?.value?.let { it as Int } ?: Int.MAX_VALUE
} }
fun PublicSettings.baseUrl(): String? = this[SITE_URL]?.value as String fun PublicSettings.baseUrl(): String? = this[SITE_URL]?.value as String?
fun PublicSettings.siteName(): String? = this[SITE_NAME]?.value as String? fun PublicSettings.siteName(): String? = this[SITE_NAME]?.value as String?
\ No newline at end of file
package chat.rocket.android.server.presentation
import chat.rocket.android.BuildConfig
import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.serverInfo
import timber.log.Timber
abstract class CheckServerPresenter constructor(private val strategy: CancelStrategy,
private val client: RocketChatClient,
private val view: VersionCheckView) {
internal fun checkServerInfo() {
launchUI(strategy) {
val serverInfo = client.serverInfo()
val thisServerVersion = serverInfo.version
val isRequiredVersion = isRequiredServerVersion(thisServerVersion)
val isRecommendedVersion = isRecommendedServerVersion(thisServerVersion)
if (isRequiredVersion) {
if (isRecommendedVersion) {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: $thisServerVersion)")
} else {
view.alertNotRecommendedVersion()
}
} else {
if (!isRecommendedVersion) {
view.blockAndAlertNotRequiredVersion()
Timber.i("Oops. Looks like your server is out-of-date! Minimum server version required ${BuildConfig.REQUIRED_SERVER_VERSION}!")
}
}
}
}
private fun isRequiredServerVersion(version: String): Boolean {
return isMinimumVersion(version, getVersionDistilled(BuildConfig.REQUIRED_SERVER_VERSION))
}
private fun isRecommendedServerVersion(version: String): Boolean {
return isMinimumVersion(version, getVersionDistilled(BuildConfig.RECOMMENDED_SERVER_VERSION))
}
private fun isMinimumVersion(version: String, required: VersionInfo): Boolean {
val thisVersion = getVersionDistilled(version)
with(thisVersion) {
if (major < required.major) {
return false
} else if (major > required.major) {
return true
}
if (minor < required.minor) {
return false
} else if (minor > required.minor) {
return true
}
return update >= required.update
}
}
private fun getVersionDistilled(version: String): VersionInfo {
var split = version.split("-")
if (split.isEmpty()) {
return VersionInfo(0, 0, 0, null, "0.0.0")
}
val ver = split[0]
var release: String? = null
if (split.size > 1) {
release = split[1]
}
split = ver.split(".")
val major = getVersionNumber(split, 0)
val minor = getVersionNumber(split, 1)
val update = getVersionNumber(split, 2)
return VersionInfo(
major = major,
minor = minor,
update = update,
release = release,
full = version)
}
private fun getVersionNumber(split: List<String>, index: Int): Int {
return try {
split.getOrNull(index)?.toInt() ?: 0
} catch (ex: NumberFormatException) {
0
}
}
}
\ No newline at end of file
package chat.rocket.android.util
data class VersionInfo(
val major: Int,
val minor: Int,
val update: Int = 0,
val release: String?,
val full: String
)
\ No newline at end of file
...@@ -78,8 +78,10 @@ class OauthWebViewActivity : AppCompatActivity() { ...@@ -78,8 +78,10 @@ class OauthWebViewActivity : AppCompatActivity() {
private fun setupWebView() { private fun setupWebView() {
with(web_view.settings) { with(web_view.settings) {
javaScriptEnabled = true javaScriptEnabled = true
// TODO This is required to make Google OAuth work, but we shoud use Custom Tabs instead. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968 // TODO Remove this workaround that is required to make Google OAuth to work. We should use Custom Tabs instead. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968
userAgentString = "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19" if (webPageUrl.contains("google")) {
userAgentString = "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/43.0.2357.65 Mobile Safari/535.19"
}
} }
web_view.webViewClient = object : WebViewClient() { web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="1055.0303"
android:viewportWidth="1055.0303">
<group
android:translateX="281.28394"
android:translateY="271.51514">
<path
android:fillColor="#CC3333"
android:pathData="M491.3,255.3c0,-24.1 -7.2,-47.2 -21.4,-68.7c-12.8,-19.3 -30.7,-36.4 -53.2,-50.7c-43.5,-27.8 -100.6,-43.1 -160.9,-43.1c-20.1,0 -40,1.7 -59.2,5.1c-11.9,-11.2 -25.9,-21.2 -40.7,-29.2c-79,-38.3 -144.6,-0.9 -144.6,-0.9s60.9,50.1 51,93.9c-27.3,27 -42,59.6 -42,93.6c0,0.1 0,0.2 0,0.3c0,0.1 0,0.2 0,0.3c0,33.9 14.8,66.6 42,93.6c9.9,43.9 -51,93.9 -51,93.9s65.5,37.4 144.6,-0.9c14.8,-8 28.8,-18 40.7,-29.2c19.2,3.4 39.1,5.1 59.2,5.1c60.3,0 117.4,-15.3 160.9,-43.1c22.5,-14.4 40.4,-31.5 53.2,-50.7c14.2,-21.5 21.4,-44.6 21.4,-68.7c0,-0.1 0,-0.2 0,-0.3C491.3,255.6 491.3,255.4 491.3,255.3z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M255.9,124.2c113.9,0 206.3,59 206.3,131.8c0,72.8 -92.4,131.8 -206.3,131.8c-25.4,0 -49.7,-2.9 -72.1,-8.3c-22.8,27.4 -73,65.6 -121.7,53.3c15.9,-17 39.4,-45.8 34.3,-93.2c-29.2,-22.7 -46.8,-51.8 -46.8,-83.5C49.6,183.2 142,124.2 255.9,124.2" />
<path
android:fillColor="#CC3333"
android:pathData="M255.9,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" />
<path
android:fillColor="#CC3333"
android:pathData="M351.2,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" />
<path
android:fillColor="#CC3333"
android:pathData="M160.6,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" />
<path
android:fillColor="#CCCCCC"
android:pathData="M255.8,372.8c-25.4,0 -56.2,-4.9 -78.7,-9.5c-20.1,21 -53.7,52.7 -99.6,50.3c-5.7,8.6 -10.2,13.5 -15.5,19.2c48.7,12.3 98.9,-25.8 121.7,-53.3c22.4,5.4 46.7,8.3 72.1,8.3c113,0 204.8,-58.1 206.3,-130C460.7,320.1 368.8,372.8 255.8,372.8z" />
<path
android:fillColor="#00000000"
android:pathData="M172,350.9"
android:strokeColor="#000000"
android:strokeWidth="1" />
<path
android:fillColor="#00000000"
android:pathData="M200.4,422.9"
android:strokeColor="#000000"
android:strokeWidth="1" />
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
<RadioGroup
android:id="@+id/radio_group_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/radio_sort_alphabetical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/dialog_sort_by_alphabet"
android:textSize="18sp" />
<RadioButton
android:id="@+id/radio_sort_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/dialog_sort_by_activity"
android:textSize="18sp" />
</RadioGroup>
<CheckBox
android:id="@+id/checkbox_group_by_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:padding="8dp"
android:text="@string/dialog_group_by_type"
android:textSize="18sp" />
<!--TODO Add checkbox for "Group favourites after sdk support"-->
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/darkGray" />
<TextView
android:id="@+id/text_chatroom_header"
style="@style/ChatRooms.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:text="@string/chatroom_header" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/darkGray" />
</LinearLayout>
\ No newline at end of file
...@@ -9,4 +9,10 @@ ...@@ -9,4 +9,10 @@
app:actionViewClass="android.support.v7.widget.SearchView" app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" /> app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/action_sort"
android:icon="@drawable/ic_sort"
android:title="@string/menu_chatroom_sort"
app:showAsAction="always" />
</menu> </menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>
\ No newline at end of file
<resources>
<!-- Titles -->
<string name="title_sign_in_your_server">अपने सर्वर में साइन इन करें</string>
<string name="title_log_in">लॉग इन करें</string>
<string name="title_sign_up">साइन अप करें</string>
<string name="title_authentication">प्रमाणीकरण</string>
<string name="title_legal_terms">कानूनी शर्तें</string>
<string name="title_chats">चैट</string>
<string name="title_profile">प्रोफाइल</string>
<string name="title_members">सदस्य (%d)</string>
<string name="title_settings">सेटिंग्स</string>
<string name="title_password">पासवर्ड बदलें</string>
<string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string>
<!-- Actions -->
<string name="action_connect">जुडिये</string>
<string name="action_login_or_sign_up">लॉग इन करने या खाता बनाने के लिए इस बटन को टैप करें</string>
<string name="action_terms_of_service">सेवा की शर्तें</string>
<string name="action_privacy_policy">गोपनीयता नीति</string>
<string name="action_search">खोजें</string>
<string name="action_update">अद्यतन करें</string>
<string name="action_settings">सेटिंग्स</string>
<string name="action_logout">लोग आउट करें</string>
<string name="action_files">फ़ाइलें</string>
<string name="action_confirm_password">पासवर्ड परिवर्तन की पुष्टि करें</string>
<string name="action_join_chat">चैट में शामिल हों</string>
<string name="action_add_account">खाता जोड़ो</string>
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_password">पासवर्ड बदलें</item>
</string-array>
<!-- Regular information messages -->
<string name="msg_no_internet_connection">कोई इंटरनेट कनेक्शन नहीं है</string>
<string name="msg_generic_error">क्षमा करें, एक त्रुटि हुई है, कृपया पुनः प्रयास करें</string>
<string name="msg_no_data_to_display">डेटा प्रदर्शित करने के लिए उपलब्ध नहीं हैं</string>
<string name="msg_profile_update_successfully">प्रोफ़ाइल सफलतापूर्वक अपडेट हो गया है</string>
<string name="msg_username">यूजरनेम</string>
<string name="msg_username_or_email">यूजरनेम या ईमेल</string>
<string name="msg_password">पासवर्ड</string>
<string name="msg_name">नाम</string>
<string name="msg_email">ईमेल</string>
<string name="msg_avatar_url">अवतार यूआरएल</string>
<string name="msg_or_continue_using_social_accounts">या सामाजिक खाते का उपयोग करना जारी रखें</string>
<string name="msg_new_user">नया उपयोगकर्ता? %1$s</string>
<string name="msg_new_user_agreement">आगे बढ़कर आप हमारे %1$s और %2$s से सहमत हो रहे हैं</string>
<string name="msg_2fa_code">कोड 2FA</string>
<string name="msg_yesterday">कल</string>
<string name="msg_message">संदेश</string>
<string name="msg_this_room_is_read_only">यह रूम केवल पढ़ने के लिए है</string>
<string name="msg_invalid_2fa_code">अमान्य 2FA कोड</string>
<string name="msg_invalid_file">अवैध फाइल</string>
<string name="msg_invalid_server_url">अमान्य सर्वर यूआरL</string>
<string name="msg_content_description_log_in_using_facebook">Facebook द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_github">Github द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_google">Google द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_linkedin">Linkedin द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_meteor">Meteor द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_twitter">Twitter द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_gitlab">Gitlab द्वारा लॉगिन करें</string>
<string name="msg_content_description_send_message">मेसेज भेजें</string>
<string name="msg_content_description_show_attachment_options">अटैचमेंट विकल्प दिखाएं</string>
<string name="msg_you">आप</string>
<string name="msg_unknown">अनजान</string>
<string name="msg_email_address">ई-मेल एड्रेस</string>
<string name="msg_utc_offset">यूटीसी ऑफ़सेट</string>
<string name="msg_new_password">नया पासवर्ड दर्ज करें</string>
<string name="msg_confirm_password">नए पासवर्ड की पुष्टि करें</string>
<string name="msg_preview_video">वीडियो</string>
<string name="msg_preview_audio">ऑडियो</string>
<string name="msg_preview_photo">तस्वीरें</string>
<string name="msg_unread_messages">अपठित संदेश</string>
<string name="msg_no_messages_yet">अभी तक कोई पोस्ट नहीं</string>
<string name="msg_requires_username">उपयोगकर्ता नाम आवश्यक है: कृपया, वेब संस्करण के माध्यम से एक उपयोगकर्ता नाम बनाएं और लॉग इन करने के लिए वापस आएँ</string>
<string name="msg_ok">OK</string>
<string name="msg_ver_not_recommended">
ऐसा लगता है कि आपका सर्वर संस्करण अनुशंसित संस्करण %1$s के नीचे है।\nआप अभी भी लॉगिन कर सकते हैं लेकिन आप अप्रत्याशित व्यवहार का अनुभव कर सकते हैं
</string>
<string name="msg_ver_not_minimum">
ऐसा लगता है कि आपका सर्वर संस्करण न्यूनतम आवश्यक संस्करण %1$s से कम है।\nकृपया लॉगिन करने के लिए अपने सर्वर को अपग्रेड करें!
</string>
<!-- System messages -->
<string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string>
<string name="message_user_added_by">उपयोगकर्ता %1$s द्वारा %2$s को जोड़ा गया</string>
<string name="message_user_removed_by">उपयोगकर्ता %1$s द्वारा %2$s को निकाला गया</string>
<string name="message_user_left">ने चैनल को छोड़ दिया है</string>
<string name="message_user_joined_channel">चैनल में शामिल हो गया है</string>
<string name="message_welcome">%s का स्वागत करते हैं</string>
<string name="message_removed">संदेश हटाया गया</string>
<string name="message_pinned">एक संदेश पिन किया:</string>
<!-- Message actions -->
<string name="action_msg_reply">जवाब दें</string>
<string name="action_msg_edit">संपादन करें</string>
<string name="action_msg_copy">कॉपी</string>
<string name="action_msg_quote">उद्धरण</string>
<string name="action_msg_delete">मिटायें</string>
<string name="action_msg_pin">संदेश को पिन करें</string>
<string name="action_msg_unpin">संदेश को पिन से हटाएँ</string>
<string name="action_msg_star">संदेश को स्टार करें</string>
<string name="action_msg_share">शेयर करें</string>
<string name="action_title_editing">संपादन संदेश</string>
<string name="action_msg_add_reaction">प्रतिक्रिया जोड़ें</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">संपादन की अनुमति नहीं है</string>
<string name="permission_deleting_not_allowed">हटाने की अनुमति नहीं है</string>
<string name="permission_pinning_not_allowed">पिनि करने की अनुमति नहीं है</string>
<!-- Members List -->
<string name="title_members_list">सदस्यों की सूची</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">पिन किए गए संदेश</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">फ़ाइल का आकार %1$d बाइट्स ने %2$d बाइट्स के अधिकतम अपलोड आकार को पार कर लिया है</string>
<!-- Socket status -->
<string name="status_connected">कनेक्टेड</string>
<string name="status_disconnected">डिस्कनेक्टेड</string>
<string name="status_connecting">कनेक्टिंग</string>
<string name="status_authenticating">प्रमाणीकरण</string>
<string name="status_disconnecting">डिसकनेक्टिंग</string>
<string name="status_waiting">%d सेकेंड में कनेक्ट हो रहा है</string>
<!--Suggestions-->
<string name="suggest_all_description">इस कमरे में सभी को सूचित करें</string>
<string name="suggest_here_description">इस कमरे में सक्रिय उपयोगकर्ताओं को सूचित करना</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">आपके संदेश से पहले ༼ つ ◕_◕ ༽つ दिखाता है</string>
<string name="Slash_LennyFace_Description">आपके संदेश के बाद ( ͡° ͜ʖ ͡°) दिखाता है</string>
<string name="Slash_Shrug_Description">आपके संदेश के बाद ¯\_(ツ)_/¯ दिखाता है</string>
<string name="Slash_Tableflip_Description">(╯°□°)╯︵ ┻━┻ दिखाता है</string>
<string name="Slash_TableUnflip_Description">┬─┬ ノ( ゜-゜ノ) दिखाता है</string>
<string name="Create_A_New_Channel">एक नया चैनल बनाएं</string>
<string name="Show_the_keyboard_shortcut_list">कीबोर्ड शॉर्टकट सूची दिखाएं</string>
<string name="Invite_user_to_join_channel_all_from">इस चैनल में शामिल होने के लिए [#channel] से सभी उपयोगकर्ताओं को आमंत्रित करें</string>
<string name="Invite_user_to_join_channel_all_to">[# चैनल] में शामिल होने के लिए सभी उपयोगकर्ताओं को इस चैनल पर आमंत्रित करें</string>
<string name="Archive">संग्रहित करें</string>
<string name="Remove_someone_from_room">रूम से किसी को निकालें</string>
<string name="Leave_the_current_channel">मौजूदा चैनल को छोड़ दें</string>
<string name="Displays_action_text">Displays action text</string>
<string name="Direct_message_someone">किसी को प्रत्यक्ष संदेश भेजें</string>
<string name="Mute_someone_in_room">रूम में किसी को म्यूट करें</string>
<string name="Unmute_someone_in_room">रूम में किसी को अनम्यूट करें</string>
<string name="Invite_user_to_join_channel">इस चैनल में शामिल होने के लिए एक उपयोगकर्ता को आमंत्रित करें</string>
<string name="Unarchive">असंग्रहित करें</string>
<string name="Join_the_given_channel">दिए गए चैनल से जुड़ें</string>
<string name="Guggy_Command_Description">दिए गए पाठ पर आधारित gif उत्पन्न करता</string>
<string name="Slash_Topic_Description">विषय सेट करें</string>
<!-- Emoji message-->
<string name="msg_no_recent_emoji"> कोई नया इमोजी नहीं</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">क्रम</string>
<string name="dialog_sort_title">द्वारा सॉर्ट करें</string>
<string name="dialog_sort_by_alphabet">वर्णानुक्रम</string>
<string name="dialog_sort_by_activity">गतिविधि</string>
<string name="dialog_group_by_type">प्रकार के आधार पर समूह</string>
<string name="dialog_group_favourites">पसंदीदा समूह</string>
<string name="chatroom_header">हैडर</string>
<!--ChatRooms Headers-->
<string name="header_channel">चैनलों</string>
<string name="header_private_groups">निजी समूहों</string>
<string name="header_direct_messages">प्रत्यक्ष संदेश</string>
<string name="header_live_chats">Live Chats</string>
<string name="header_unknown">अज्ञात</string>
</resources>
\ No newline at end of file
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
<string name="action_join_chat">Entrar no Chat</string> <string name="action_join_chat">Entrar no Chat</string>
<string name="action_add_account">Adicionar Conta</string> <string name="action_add_account">Adicionar Conta</string>
<!-- 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>
...@@ -74,6 +73,14 @@ ...@@ -74,6 +73,14 @@
<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_requires_username">Nome de usuário requerido: Por favor, crie um nome de usuário através da versão web e volte para fazer login.</string>
<string name="msg_ok">OK</string>
<string name="msg_ver_not_recommended">
Parece que a versão do seu servidor está abaixo da recomendada %1$s.\nVocê ainda assim pode logar e continuar mas podem ocorrer alguns problemas inesperados.
</string>
<string name="msg_ver_not_minimum">
Parece que a versão do seu servidor está abaixo da mínima requerida %1$s.\nPor favor, atualize seus servidores antes de continuar!
</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">Nome da sala alterado para: %1$s por %2$s</string> <string name="message_room_name_changed">Nome da sala alterado para: %1$s por %2$s</string>
...@@ -149,4 +156,20 @@ ...@@ -149,4 +156,20 @@
<!-- Emoji message--> <!-- Emoji message-->
<string name="msg_no_recent_emoji">Nenhum emoji recente</string> <string name="msg_no_recent_emoji">Nenhum emoji recente</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Ordenar</string>
<string name="dialog_sort_title">Ordenar por</string>
<string name="dialog_sort_by_alphabet">Alfabeticamente</string>
<string name="dialog_sort_by_activity">Atividade</string>
<string name="dialog_group_by_type">Agrupar por tipo</string>
<string name="dialog_group_favourites">Grupos favoritos</string>
<string name="chatroom_header">Cabeçalho</string>
<!--ChatRooms Headers-->
<string name="header_channel">Canais</string>
<string name="header_private_groups">Grupos Privados</string>
<string name="header_direct_messages">Mensagens diretas</string>
<string name="header_live_chats">Live Chats</string>
<string name="header_unknown">Desconhecido</string>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#15293F</color>
<!-- Main colors --> <!-- Main colors -->
<color name="colorPrimary">#FF303030</color> <!-- Material Grey 850 --> <color name="colorPrimary">#FF303030</color> <!-- Material Grey 850 -->
...@@ -17,6 +16,8 @@ ...@@ -17,6 +16,8 @@
<color name="colorUserStatusAway">#FDD236</color> <color name="colorUserStatusAway">#FDD236</color>
<color name="colorUserStatusOffline">#d9d9d9</color> <color name="colorUserStatusOffline">#d9d9d9</color>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="colorDrawableTintGrey">#9FA2A8</color> <color name="colorDrawableTintGrey">#9FA2A8</color>
<color name="colorDividerMessageComposer">#D8D8D8</color> <color name="colorDividerMessageComposer">#D8D8D8</color>
......
...@@ -75,6 +75,13 @@ ...@@ -75,6 +75,13 @@
<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_requires_username">Username required: Please, create an username through the web version and come back to log in.</string>
<string name="msg_ok">OK</string>
<string name="msg_ver_not_recommended">
Looks like your server version is below the recommended version %1$s.\nYou can still login but you may experience unexpected behaviors.</string>
<string name="msg_ver_not_minimum">
Looks like your server version is below the minimum required version %1$s.\nPlease upgrade your server to login!
</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">Room name changed to: %1$s by %2$s</string> <string name="message_room_name_changed">Room name changed to: %1$s by %2$s</string>
...@@ -150,4 +157,20 @@ ...@@ -150,4 +157,20 @@
<!-- Emoji message--> <!-- Emoji message-->
<string name="msg_no_recent_emoji">No recent emoji</string> <string name="msg_no_recent_emoji">No recent emoji</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Sort</string>
<string name="dialog_sort_title">Sort by</string>
<string name="dialog_sort_by_alphabet">Alphabetical</string>
<string name="dialog_sort_by_activity">Activity</string>
<string name="dialog_group_by_type">Group by type</string>
<string name="dialog_group_favourites">Group favourites</string>
<string name="chatroom_header">Header</string>
<!--ChatRooms Headers-->
<string name="header_channel">Channels</string>
<string name="header_private_groups">Private Groups</string>
<string name="header_direct_messages">Direct Messages</string>
<string name="header_live_chats">Live Chats</string>
<string name="header_unknown">Unknown</string>
</resources> </resources>
\ No newline at end of file
...@@ -71,6 +71,10 @@ ...@@ -71,6 +71,10 @@
<item name="android:paddingStart">@dimen/edit_text_margin</item> <item name="android:paddingStart">@dimen/edit_text_margin</item>
</style> </style>
<style name="ChatRooms.Header" parent="TextAppearance.AppCompat.Headline">
<item name="android:textSize">16sp</item>
</style>
<style name="ChatRoom.Name.TextView" parent="TextAppearance.AppCompat.Title"> <style name="ChatRoom.Name.TextView" parent="TextAppearance.AppCompat.Title">
<item name="android:ellipsize">end</item> <item name="android:ellipsize">end</item>
<item name="android:maxLines">1</item> <item name="android:maxLines">1</item>
......
...@@ -10,7 +10,7 @@ buildscript { ...@@ -10,7 +10,7 @@ buildscript {
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:3.0.1" classpath 'com.android.tools.build:gradle:3.1.0'
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'
......
...@@ -9,7 +9,7 @@ ext { ...@@ -9,7 +9,7 @@ ext {
dokka : '0.9.16', dokka : '0.9.16',
// Main dependencies // Main dependencies
support : '27.0.2', support : '27.1.0',
constraintLayout : '1.0.2', constraintLayout : '1.0.2',
androidKtx : '0.2', androidKtx : '0.2',
dagger : '2.14.1', dagger : '2.14.1',
...@@ -20,7 +20,7 @@ ext { ...@@ -20,7 +20,7 @@ ext {
rxAndroid : '2.0.2', rxAndroid : '2.0.2',
moshi : '1.6.0-SNAPSHOT', moshi : '1.6.0-SNAPSHOT',
okhttp : '3.10.0', okhttp : '3.10.0',
timber : '4.6.1', timber : '4.7.0',
threeTenABP : '1.0.5', threeTenABP : '1.0.5',
rxBinding : '2.0.0', rxBinding : '2.0.0',
fresco : '1.8.1', fresco : '1.8.1',
...@@ -37,67 +37,68 @@ ext { ...@@ -37,67 +37,68 @@ ext {
expresso : '3.0.1', expresso : '3.0.1',
mockito : '2.10.0' mockito : '2.10.0'
] ]
libraries = [ libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}", kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}",
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}", coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}", coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
appCompat : "com.android.support:appcompat-v7:${versions.support}", appCompat : "com.android.support:appcompat-v7:${versions.support}",
annotations : "com.android.support:support-annotations:${versions.support}", annotations : "com.android.support:support-annotations:${versions.support}",
recyclerview : "com.android.support:recyclerview-v7:${versions.support}", recyclerview : "com.android.support:recyclerview-v7:${versions.support}",
design : "com.android.support:design:${versions.support}", design : "com.android.support:design:${versions.support}",
constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}", constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}",
cardView : "com.android.support:cardview-v7:${versions.support}", cardView : "com.android.support:cardview-v7:${versions.support}",
flexbox : "com.google.android:flexbox:${versions.flexbox}", flexbox : "com.google.android:flexbox:${versions.flexbox}",
customTabs : "com.android.support:customtabs:${versions.support}",
androidKtx : "androidx.core:core-ktx:${versions.androidKtx}",
androidKtx : "androidx.core:core-ktx:${versions.androidKtx}",
dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}", dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}", daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}", daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
playServicesGcm : "com.google.android.gms:play-services-gcm:${versions.playServices}", daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}", playServicesGcm : "com.google.android.gms:play-services-gcm:${versions.playServices}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
room : "android.arch.persistence.room:runtime:${versions.room}",
roomProcessor : "android.arch.persistence.room:compiler:${versions.room}", room : "android.arch.persistence.room:runtime:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}", roomProcessor : "android.arch.persistence.room:compiler:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}",
rxKotlin : "io.reactivex.rxjava2:rxkotlin:${versions.rxKotlin}",
rxAndroid : "io.reactivex.rxjava2:rxandroid:${versions.rxAndroid}", rxKotlin : "io.reactivex.rxjava2:rxkotlin:${versions.rxKotlin}",
rxAndroid : "io.reactivex.rxjava2:rxandroid:${versions.rxAndroid}",
moshi : "com.squareup.moshi:moshi:${versions.moshi}",
moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshi}", moshi : "com.squareup.moshi:moshi:${versions.moshi}",
okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}", moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshi}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}", okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}",
timber : "com.jakewharton.timber:timber:${versions.timber}",
threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}", timber : "com.jakewharton.timber:timber:${versions.timber}",
rxBinding : "com.jakewharton.rxbinding2:rxbinding-kotlin:${versions.rxBinding}", threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}",
rxBinding : "com.jakewharton.rxbinding2:rxbinding-kotlin:${versions.rxBinding}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoOkHttp : "com.facebook.fresco:imagepipeline-okhttp3:${versions.fresco}", fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}", frescoOkHttp : "com.facebook.fresco:imagepipeline-okhttp3:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}", frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}", frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}",
kotshiApi : "se.ansman.kotshi:api:${versions.kotshi}",
kotshiCompiler : "se.ansman.kotshi:compiler:${versions.kotshi}", kotshiApi : "se.ansman.kotshi:api:${versions.kotshi}",
kotshiCompiler : "se.ansman.kotshi:compiler:${versions.kotshi}",
frescoImageViewer : "com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}",
frescoImageViewer : "com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}",
markwon : "ru.noties:markwon:${versions.markwon}",
markwonImageLoader : "ru.noties:markwon-image-loader:${versions.markwon}", markwon : "ru.noties:markwon:${versions.markwon}",
markwonImageLoader : "ru.noties:markwon-image-loader:${versions.markwon}",
sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}",
sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}",
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
// For testing
junit : "junit:junit:$versions.junit", // For testing
expressoCore : "com.android.support.test.espresso:espresso-core:${versions.expresso}", junit : "junit:junit:$versions.junit",
roomTest : "android.arch.persistence.room:testing:${versions.room}", expressoCore : "com.android.support.test.espresso:espresso-core:${versions.expresso}",
truth : "com.google.truth:truth:$versions.truth", roomTest : "android.arch.persistence.room:testing:${versions.room}",
truth : "com.google.truth:truth:$versions.truth",
] ]
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment