Commit 26ead887 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Merge branch 'beta' of github.com:RocketChat/Rocket.Chat.Android into develop

parents d46037d4 9da37707
......@@ -13,7 +13,7 @@ android {
applicationId "chat.rocket.android"
minSdkVersion 21
targetSdkVersion versions.targetSdk
versionCode 2022
versionCode 2024
versionName "2.3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
......@@ -26,12 +26,19 @@ android {
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
debug {
storeFile project.rootProject.file('debug.keystore').getCanonicalFile()
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
buildTypes {
release {
buildConfigField "String", "REQUIRED_SERVER_VERSION", '"0.62.0"'
buildConfigField "String", "RECOMMENDED_SERVER_VERSION", '"0.63.0"'
buildConfigField "String", "RECOMMENDED_SERVER_VERSION", '"0.64.2"'
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
......@@ -39,7 +46,8 @@ android {
debug {
buildConfigField "String", "REQUIRED_SERVER_VERSION", '"0.62.0"'
buildConfigField "String", "RECOMMENDED_SERVER_VERSION", '"0.63.0"'
buildConfigField "String", "RECOMMENDED_SERVER_VERSION", '"0.64.2"'
signingConfig signingConfigs.debug
applicationIdSuffix ".dev"
}
}
......@@ -73,7 +81,7 @@ dependencies {
kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt
implementation libraries.playServicesGcm
implementation libraries.fcm
implementation libraries.playServicesAuth
implementation libraries.room
......
{
"project_info": {
"project_number": "1020987621558",
"firebase_url": "https://rocketchatnative.firebaseio.com",
"project_id": "rocketchatnative",
"storage_bucket": "rocketchatnative.appspot.com"
"project_number": "673693445664",
"firebase_url": "https://rocketchat-9e9be.firebaseio.com",
"project_id": "rocketchat-9e9be",
"storage_bucket": "rocketchat-9e9be.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1020987621558:android:16da2e50aff9f0c9",
"mobilesdk_app_id": "1:673693445664:android:6ef4638e500ec958",
"android_client_info": {
"package_name": "chat.rocket.android"
"package_name": "RocketChat"
}
},
"oauth_client": [
{
"client_id": "1020987621558-trk61fjrahho0ujtjap095p1jmi48pfq.apps.googleusercontent.com",
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-vtcvuvso7k88gpodlpshod55g1ehs03s.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-sf7lqf11kk6vplg9ljh7pi491gvb08f3.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-fb71j3aqmafmm20jkj8gvpusv04fdnq8.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-ejd5lkbsdjoo5onc052dotsjacdh1kcc.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-ssfpeb0are3svvg0etbttog789s0n3ua.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDc7VYUdU6kRkoRTToiCn1rh-W0wJvhLWk"
"current_key": "AIzaSyDIkZj1TRz8TmhnMswDwVY5OnWuzFK3rxg"
}
],
"services": {
......@@ -39,20 +63,143 @@
},
{
"client_info": {
"mobilesdk_app_id": "1:1020987621558:android:1551054db195f705",
"mobilesdk_app_id": "1:673693445664:android:16da2e50aff9f0c9",
"android_client_info": {
"package_name": "chat.rocket.android"
}
},
"oauth_client": [
{
"client_id": "673693445664-k0mvosdjoe5dbvqce3b377ckabb5dgu8.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "chat.rocket.android",
"certificate_hash": "33fa8582794176014a59054192e261bfad0e5273"
}
},
{
"client_id": "673693445664-hrjftksij02vqtd467ln2cubvu48ft5j.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "chat.rocket.android",
"certificate_hash": "41cf750df786a6d9da712a98a629d0c8391876d6"
}
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-vtcvuvso7k88gpodlpshod55g1ehs03s.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-sf7lqf11kk6vplg9ljh7pi491gvb08f3.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-fb71j3aqmafmm20jkj8gvpusv04fdnq8.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-ejd5lkbsdjoo5onc052dotsjacdh1kcc.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-ssfpeb0are3svvg0etbttog789s0n3ua.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDIkZj1TRz8TmhnMswDwVY5OnWuzFK3rxg"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 2,
"other_platform_oauth_client": [
{
"client_id": "673693445664-pa3k48sg81r89rn65e9rlnu4gpmm5vem.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.konecty.rocket.chat"
}
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
}
]
},
"ads_service": {
"status": 2
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:673693445664:android:1551054db195f705",
"android_client_info": {
"package_name": "chat.rocket.android.dev"
}
},
"oauth_client": [
{
"client_id": "1020987621558-trk61fjrahho0ujtjap095p1jmi48pfq.apps.googleusercontent.com",
"client_id": "673693445664-t5aeku0oie010npd40a0tgn27c418vk7.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "chat.rocket.android.dev",
"certificate_hash": "41cf750df786a6d9da712a98a629d0c8391876d6"
}
},
{
"client_id": "673693445664-iml14ln4vccuu7liclrpt2k671fkjs38.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "chat.rocket.android.dev",
"certificate_hash": "33fa8582794176014a59054192e261bfad0e5273"
}
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-vtcvuvso7k88gpodlpshod55g1ehs03s.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-sf7lqf11kk6vplg9ljh7pi491gvb08f3.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-fb71j3aqmafmm20jkj8gvpusv04fdnq8.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-ejd5lkbsdjoo5onc052dotsjacdh1kcc.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-ssfpeb0are3svvg0etbttog789s0n3ua.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDc7VYUdU6kRkoRTToiCn1rh-W0wJvhLWk"
"current_key": "AIzaSyDIkZj1TRz8TmhnMswDwVY5OnWuzFK3rxg"
}
],
"services": {
......@@ -60,8 +207,95 @@
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
"status": 2,
"other_platform_oauth_client": [
{
"client_id": "673693445664-pa3k48sg81r89rn65e9rlnu4gpmm5vem.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.konecty.rocket.chat"
}
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
}
]
},
"ads_service": {
"status": 2
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:673693445664:android:64932c99863e2838",
"android_client_info": {
"package_name": "com.konecty.rocket.chat"
}
},
"oauth_client": [
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-vtcvuvso7k88gpodlpshod55g1ehs03s.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-sf7lqf11kk6vplg9ljh7pi491gvb08f3.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-fb71j3aqmafmm20jkj8gvpusv04fdnq8.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-ejd5lkbsdjoo5onc052dotsjacdh1kcc.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-3ajben08beuco6eout3kpod2gbbm8fij.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.konecty.rocket.chat",
"certificate_hash": "cd5806ba3f0141d0f2e47acfe64a485f575108ab"
}
},
{
"client_id": "673693445664-ssfpeb0are3svvg0etbttog789s0n3ua.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDIkZj1TRz8TmhnMswDwVY5OnWuzFK3rxg"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 2,
"other_platform_oauth_client": [
{
"client_id": "673693445664-pa3k48sg81r89rn65e9rlnu4gpmm5vem.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.konecty.rocket.chat"
}
},
{
"client_id": "673693445664-97s9t777ful7mn2510vuhb48958qd9tb.apps.googleusercontent.com",
"client_type": 3
}
]
},
"ads_service": {
"status": 2
......
......@@ -3,17 +3,9 @@
package="chat.rocket.android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<application
android:name=".app.RocketChatApplication"
android:allowBackup="true"
......@@ -39,6 +31,7 @@
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
......@@ -91,18 +84,6 @@
android:name=".settings.about.ui.AboutActivity"
android:theme="@style/AppTheme" />
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
<receiver
android:name=".push.DirectReplyReceiver"
android:enabled="true"
......@@ -121,10 +102,11 @@
</service>
<service
android:name=".push.GcmListenerService"
android:exported="false">
android:name=".push.FirebaseMessagingService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
......
......@@ -14,11 +14,11 @@ object DateTimeHelper {
/**
* Returns a [LocalDateTime] from a [Long].
*
* @param long The [Long]
* @param long The [Long] to gets a [LocalDateTime].
* @return The [LocalDateTime] from a [Long].
*/
fun getLocalDateTime(long: Long?): LocalDateTime {
return LocalDateTime.ofInstant(long?.let { Instant.ofEpochMilli(it) }, ZoneId.systemDefault())
fun getLocalDateTime(long: Long): LocalDateTime {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(long), ZoneId.systemDefault())
}
/**
......
......@@ -56,7 +56,6 @@ class LoginPresenter @Inject constructor(
private lateinit var credentialSecret: String
private lateinit var deepLinkUserId: String
private lateinit var deepLinkToken: String
private var loginCredentials: Credential? = null
fun setupView() {
setupConnectionInfo(currentServer)
......@@ -357,10 +356,7 @@ class LoginPresenter @Inject constructor(
saveToken(token)
registerPushToken()
if (loginType == TYPE_LOGIN_USER_EMAIL) {
loginCredentials = Credential.Builder(usernameOrEmail)
.setPassword(password)
.build()
view.saveSmartLockCredentials(loginCredentials)
view.saveSmartLockCredentials(usernameOrEmail, password)
}
navigator.toChatList()
} else if (loginType == TYPE_LOGIN_OAUTH) {
......
......@@ -243,7 +243,7 @@ interface LoginView : LoadingView, MessageView {
fun alertWrongPassword()
/**
* Save credentials via google smart lock
* Saves Google Smart Lock credentials.
*/
fun saveSmartLockCredentials(loginCredential: Credential?)
fun saveSmartLockCredentials(id: String, password: String)
}
\ No newline at end of file
......@@ -2,28 +2,27 @@ package chat.rocket.android.authentication.login.ui
import DrawableHelper
import android.app.Activity
import android.app.PendingIntent
import android.content.Intent
import android.content.IntentSender
import android.graphics.PorterDuff
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.*
import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.ScrollView
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import chat.rocket.android.R
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.helper.*
import chat.rocket.android.util.extensions.*
import chat.rocket.android.webview.sso.ui.INTENT_SSO_TOKEN
import chat.rocket.android.webview.sso.ui.ssoWebViewIntent
......@@ -31,37 +30,25 @@ import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import chat.rocket.common.util.ifNull
import com.google.android.gms.auth.api.Auth
import com.google.android.gms.auth.api.credentials.*
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.common.api.ResolvingResultCallbacks
import com.google.android.gms.common.api.Status
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import timber.log.Timber
import javax.inject.Inject
internal const val REQUEST_CODE_FOR_CAS = 1
internal const val REQUEST_CODE_FOR_SAML = 2
internal const val REQUEST_CODE_FOR_OAUTH = 3
internal const val MULTIPLE_CREDENTIALS_READ = 4
internal const val NO_CREDENTIALS_EXIST = 5
internal const val SAVE_CREDENTIALS = 6
internal const val REQUEST_CODE_FOR_CAS = 4
internal const val REQUEST_CODE_FOR_SAML = 5
internal const val REQUEST_CODE_FOR_OAUTH = 6
lateinit var googleApiClient: GoogleApiClient
class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks {
class LoginFragment : Fragment(), LoginView {
@Inject
lateinit var presenter: LoginPresenter
private var isOauthViewEnable = false
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
areLoginOptionsNeeded()
}
private var isOauthSuccessful = false
private var isGlobalLayoutListenerSetUp = false
private var deepLinkInfo: LoginDeepLinkInfo? = null
private var credentialsToBeSaved: Credential? = null
private val credentialsClient by lazy { Credentials.getClient(requireActivity()) }
companion object {
private const val DEEP_LINK_INFO = "DeepLinkInfo"
......@@ -73,17 +60,9 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
}
}
override fun onConnected(bundle: Bundle?) {
saveSmartLockCredentials(credentialsToBeSaved)
}
override fun onConnectionSuspended(errorCode: Int) {
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
buildGoogleApiClient()
deepLinkInfo = arguments?.getParcelable(DEEP_LINK_INFO)
}
......@@ -120,156 +99,40 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
when (requestCode) {
REQUEST_CODE_FOR_CAS -> data.apply {
presenter.authenticateWithCas(getStringExtra(INTENT_SSO_TOKEN))
REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION -> {
onCredentialRetrieved(data.getParcelableExtra(Credential.EXTRA_KEY))
}
REQUEST_CODE_FOR_SIGN_IN_REQUIRED -> {
//use the hints to autofill sign in forms to reduce the info to be filled.
val credential: Credential = data.getParcelableExtra(Credential.EXTRA_KEY)
text_username_or_email.setText(credential.id)
text_password.setText(credential.password)
}
REQUEST_CODE_FOR_SAVE_RESOLUTION -> {
showMessage(getString(R.string.message_credentials_saved_successfully))
}
REQUEST_CODE_FOR_CAS -> {
presenter.authenticateWithCas(data.getStringExtra(INTENT_SSO_TOKEN))
}
REQUEST_CODE_FOR_SAML -> data.apply {
presenter.authenticateWithSaml(getStringExtra(INTENT_SSO_TOKEN))
}
REQUEST_CODE_FOR_OAUTH -> {
isOauthSuccessful = true
data.apply {
presenter.authenticateWithOauth(
getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN),
getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
)
}
}
MULTIPLE_CREDENTIALS_READ -> {
val loginCredentials: Credential =
data.getParcelableExtra(Credential.EXTRA_KEY)
handleCredential(loginCredentials)
}
NO_CREDENTIALS_EXIST -> {
//use the hints to autofill sign in forms to reduce the info to be filled
val loginCredentials: Credential =
data.getParcelableExtra(Credential.EXTRA_KEY)
val email = loginCredentials.id
val password = loginCredentials.password
text_username_or_email.setText(email)
text_password.setText(password)
}
SAVE_CREDENTIALS -> Toast.makeText(
context,
getString(R.string.message_credentials_saved_successfully),
Toast.LENGTH_SHORT
).show()
}
}
}
//cancel button pressed by the user in case of reading from smart lock
else if (resultCode == Activity.RESULT_CANCELED && requestCode == REQUEST_CODE_FOR_OAUTH) {
Timber.d("Returned from oauth")
}
}
override fun onDestroy() {
super.onDestroy()
googleApiClient.let {
activity?.let { it1 -> it.stopAutoManage(it1) }
it.disconnect()
}
}
private fun buildGoogleApiClient() {
googleApiClient = GoogleApiClient.Builder(context!!)
.enableAutoManage(activity as FragmentActivity, {
Timber.e("ERROR: Connection to client failed")
})
.addConnectionCallbacks(this)
.addApi(Auth.CREDENTIALS_API)
.build()
}
override fun onStart() {
super.onStart()
if (!isOauthSuccessful) {
requestCredentials()
}
}
private fun requestCredentials() {
val request: CredentialRequest = CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.build()
Auth.CredentialsApi.request(googleApiClient, request)
.setResultCallback { credentialRequestResult ->
val status = credentialRequestResult.status
when {
status.isSuccess -> handleCredential(credentialRequestResult.credential)
(status.statusCode == CommonStatusCodes.RESOLUTION_REQUIRED) -> resolveResult(
status,
MULTIPLE_CREDENTIALS_READ
)
(status.statusCode == CommonStatusCodes.SIGN_IN_REQUIRED) -> {
val hintRequest: HintRequest = HintRequest.Builder()
.setHintPickerConfig(
CredentialPickerConfig.Builder()
.setShowCancelButton(true)
.build()
data.getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN),
data.getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
)
.setEmailAddressIdentifierSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE)
.build()
val intent: PendingIntent =
Auth.CredentialsApi.getHintPickerIntent(googleApiClient, hintRequest)
try {
startIntentSenderForResult(
intent.intentSender,
NO_CREDENTIALS_EXIST,
null,
0,
0,
0,
null
)
} catch (e: IntentSender.SendIntentException) {
Timber.e("ERROR: Could not start hint picker Intent")
}
}
else -> Timber.d("ERROR: nothing happening")
}
}
}
private fun handleCredential(loginCredentials: Credential) {
if (loginCredentials.accountType == null) {
presenter.authenticateWithUserAndPassword(
loginCredentials.id,
loginCredentials.password.toString()
)
}
}
private fun resolveResult(status: Status, requestCode: Int) {
try {
status.startResolutionForResult(activity, requestCode)
} catch (e: IntentSender.SendIntentException) {
Timber.e("Failed to send Credentials intent")
}
}
override fun saveSmartLockCredentials(loginCredential: Credential?) {
credentialsToBeSaved = loginCredential
if (credentialsToBeSaved == null) {
return
}
activity?.let {
Auth.CredentialsApi.save(googleApiClient, credentialsToBeSaved).setResultCallback(
object : ResolvingResultCallbacks<Status>(it, SAVE_CREDENTIALS) {
override fun onSuccess(status: Status) {
Timber.d("credentials save:SUCCESS:$status")
credentialsToBeSaved = null
}
override fun onUnresolvableFailure(status: Status) {
Timber.e("credentials save:FAILURE:$status")
credentialsToBeSaved = null
}
})
override fun onResume() {
super.onResume()
image_key.setOnClickListener {
requestStoredCredentials()
image_key.isVisible = false
}
}
......@@ -289,6 +152,24 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
}
}
private fun requestStoredCredentials() {
activity?.let {
SmartLockHelper.requestStoredCredentials(credentialsClient, it)?.let {
onCredentialRetrieved(it)
}
}
}
private fun onCredentialRetrieved(credential: Credential) {
presenter.authenticateWithUserAndPassword(credential.id, credential.password.toString())
}
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.let {
SmartLockHelper.save(credentialsClient, it, id, password)
}
}
override fun showLoading() {
ui {
view_loading.isVisible = true
......@@ -321,6 +202,7 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
ui {
text_username_or_email.isVisible = true
text_password.isVisible = true
image_key.isVisible = true
}
}
......
......@@ -15,7 +15,6 @@ import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.signup
import chat.rocket.core.model.Myself
import com.google.android.gms.auth.api.credentials.Credential
import javax.inject.Inject
class SignupPresenter @Inject constructor(
......@@ -64,10 +63,7 @@ class SignupPresenter @Inject constructor(
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me)
registerPushToken()
val loginCredentials = Credential.Builder(email)
.setPassword(password)
.build()
view.saveSmartLockCredentials(loginCredentials)
view.saveSmartLockCredentials(username, password)
navigator.toChatList()
} catch (exception: RocketChatException) {
exception.message?.let {
......
......@@ -27,7 +27,7 @@ interface SignupView : LoadingView, MessageView {
fun alertBlankEmail()
/**
* Save credentials via google smart lock
* Saves Google Smart Lock credentials.
*/
fun saveSmartLockCredentials(loginCredential: Credential)
fun saveSmartLockCredentials(id: String, password: String)
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.ui
import DrawableHelper
import android.app.Activity.RESULT_OK
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
......@@ -11,30 +11,24 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.authentication.login.ui.googleApiClient
import chat.rocket.android.R.string.message_credentials_saved_successfully
import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.SmartLockHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.extensions.*
import com.google.android.gms.auth.api.Auth
import com.google.android.gms.auth.api.credentials.Credential
import com.google.android.gms.common.api.ResolvingResultCallbacks
import com.google.android.gms.common.api.Status
import com.google.android.gms.auth.api.credentials.Credentials
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import timber.log.Timber
import javax.inject.Inject
internal const val SAVE_CREDENTIALS = 1
class SignupFragment : Fragment(), SignupView {
@Inject
lateinit var presenter: SignupPresenter
private lateinit var credentialsToBeSaved: Credential
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(relative_layout.rootView)) {
bottom_container.setVisible(false)
......@@ -120,41 +114,13 @@ class SignupFragment : Fragment(), SignupView {
}
}
override fun saveSmartLockCredentials(loginCredential: Credential) {
credentialsToBeSaved = loginCredential
googleApiClient.let {
if (it.isConnected) {
saveCredentials()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
if (requestCode == SAVE_CREDENTIALS) {
if (resultCode == RESULT_OK) {
Toast.makeText(
context,
getString(R.string.message_credentials_saved_successfully),
Toast.LENGTH_SHORT
).show()
} else {
Timber.e("ERROR: Cancelled by user")
}
}
showMessage(getString(message_credentials_saved_successfully))
}
private fun saveCredentials() {
activity?.let {
Auth.CredentialsApi.save(googleApiClient, credentialsToBeSaved).setResultCallback(
object : ResolvingResultCallbacks<Status>(it, SAVE_CREDENTIALS) {
override fun onSuccess(status: Status) {
Timber.d("save:SUCCESS:$status")
}
override fun onUnresolvableFailure(status: Status) {
Timber.e("save:FAILURE:$status")
}
})
}
}
......@@ -188,6 +154,12 @@ class SignupFragment : Fragment(), SignupView {
showMessage(getString(R.string.msg_generic_error))
}
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.let {
SmartLockHelper.save(Credentials.getClient(it), it, id, password)
}
}
private fun tintEditTextDrawableStart() {
ui {
val personDrawable =
......
......@@ -30,7 +30,7 @@ class ImageAttachmentViewHolder(
file_name.text = data.attachmentTitle
image_attachment.setOnClickListener {
ImageHelper.openImage(
it.context,
context,
data.attachmentUrl,
data.attachmentTitle.toString()
)
......
......@@ -181,11 +181,9 @@ class ChatRoomPresenter @Inject constructor(
}
subscribeTypingStatus()
if (offset == 0L) {
subscribeState()
}
}
}
private suspend fun loadAndShowMessages(chatRoomId: String, chatRoomType: String, offset: Long = 0) {
val messages =
......@@ -229,10 +227,9 @@ class ChatRoomPresenter @Inject constructor(
)
try {
messagesRepository.save(newMessage)
val message = client.sendMessage(id, chatRoomId, text)
view.showNewMessage(mapper.map(newMessage, RoomViewModel(
roles = chatRoles, isBroadcast = chatIsBroadcast)))
message
client.sendMessage(id, chatRoomId, text)
} catch (ex: Exception) {
// Ok, not very beautiful, but the backend sends us a not valid response
// When someone sends a message on a read-only channel, so we just ignore it
......@@ -324,7 +321,7 @@ class ChatRoomPresenter @Inject constructor(
}
}
private fun subscribeState() {
private suspend fun subscribeState() {
Timber.d("Subscribing to Status changes")
lastState = manager.state
manager.addStatusChannel(stateChannel)
......@@ -790,6 +787,7 @@ class ChatRoomPresenter @Inject constructor(
}
private suspend fun subscribeTypingStatus() {
launch(CommonPool + strategy.jobs) {
client.subscribeTypingStatus(chatRoomId.toString()) { _, id ->
typingStatusSubscriptionId = id
}
......@@ -798,6 +796,7 @@ class ChatRoomPresenter @Inject constructor(
processTypingStatus(typingStatus)
}
}
}
private fun processTypingStatus(typingStatus: Pair<String, Boolean>) {
if (!typingStatusList.any { username -> username == typingStatus.first }) {
......@@ -837,6 +836,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) {
val viewModelStreamedMessage = mapper.map(streamedMessage, RoomViewModel(
roles = chatRoles, isBroadcast = chatIsBroadcast))
val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId)
val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id }
if (index > -1) {
......
......@@ -64,7 +64,7 @@ class MessageService : JobService() {
Timber.e(ex)
// TODO - remove the generic message when we implement :userId:/message subscription
if (ex is IllegalStateException) {
Timber.d(ex, "Probably a read-only problem...")
Timber.e(ex, "Probably a read-only problem...")
// TODO: For now we are only going to reschedule when api is fixed.
messageRepository.removeById(message.id)
jobFinished(params, false)
......
......@@ -31,6 +31,8 @@ import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.util.extensions.*
import chat.rocket.android.widget.emoji.*
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection
......@@ -243,7 +245,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(
chatRoomType, chatRoomName, presenter,
chatRoomType,
chatRoomName,
presenter,
reactionListener = this@ChatRoomFragment
)
recycler_view.adapter = adapter
......@@ -272,10 +276,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
) {
// TODO: We should rely solely on the user being able to post, but we cannot guarantee
// that the "(channels|groups).roles" endpoint is supported by the server in use.
ui {
setupMessageComposer(userCanPost)
isBroadcastChannel = channelIsBroadcast
if (isBroadcastChannel && !userCanMod) activity?.invalidateOptionsMenu()
}
}
override fun openDirectMessage(chatRoom: ChatRoom, permalink: String) {
......@@ -647,12 +653,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if (isChatRoomReadOnly && !canPost) {
text_room_is_read_only.setVisible(true)
input_container.setVisible(false)
} else if (!isSubscribed) {
} else if (!isSubscribed && roomTypeOf(chatRoomType) != RoomType.DIRECT_MESSAGE) {
input_container.setVisible(false)
button_join_chat.setVisible(true)
button_join_chat.setOnClickListener { presenter.joinChat(chatRoomId) }
} else {
button_send.alpha = 0f
button_send.setVisible(false)
button_show_attachment_options.alpha = 1f
button_show_attachment_options.setVisible(true)
......@@ -788,14 +793,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupComposeButtons(charSequence: CharSequence) {
if (charSequence.isNotEmpty() && playComposeMessageButtonsAnimation) {
button_show_attachment_options.fadeOut(1F, 0F, 120)
button_send.fadeIn(0F, 1F, 120)
button_show_attachment_options.setVisible(false)
button_send.setVisible(true)
playComposeMessageButtonsAnimation = false
}
if (charSequence.isEmpty()) {
button_send.fadeOut(1F, 0F, 120)
button_show_attachment_options.fadeIn(0F, 1F, 120)
button_send.setVisible(false)
button_show_attachment_options.setVisible(true)
playComposeMessageButtonsAnimation = true
}
}
......
......@@ -65,24 +65,35 @@ class ViewModelMapper @Inject constructor(
private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
suspend fun map(message: Message, roomViewModel: RoomViewModel = RoomViewModel(
roles = emptyList(), isBroadcast = true)): List<BaseViewModel<*>> {
suspend fun map(
message: Message,
roomViewModel: RoomViewModel = RoomViewModel(roles = emptyList(), isBroadcast = true)
): List<BaseViewModel<*>> {
return translate(message, roomViewModel)
}
suspend fun map(messages: List<Message>, roomViewModel: RoomViewModel = RoomViewModel(
roles = emptyList(), isBroadcast = true)): List<BaseViewModel<*>> = withContext(CommonPool) {
suspend fun map(
messages: List<Message>,
roomViewModel: RoomViewModel = RoomViewModel(roles = emptyList(), isBroadcast = true),
asNotReversed: Boolean = false
): List<BaseViewModel<*>> =
withContext(CommonPool) {
val list = ArrayList<BaseViewModel<*>>(messages.size)
messages.forEach {
list.addAll(translate(it, roomViewModel))
list.addAll(
if (asNotReversed) translateAsNotReversed(it, roomViewModel)
else translate(it, roomViewModel)
)
}
return@withContext list
}
private suspend fun translate(message: Message, roomViewModel: RoomViewModel)
: List<BaseViewModel<*>> = withContext(CommonPool) {
private suspend fun translate(
message: Message,
roomViewModel: RoomViewModel
): List<BaseViewModel<*>> =
withContext(CommonPool) {
val list = ArrayList<BaseViewModel<*>>()
message.urls?.forEach {
......@@ -118,6 +129,56 @@ class ViewModelMapper @Inject constructor(
return@withContext list
}
private suspend fun translateAsNotReversed(
message: Message,
roomViewModel: RoomViewModel
): List<BaseViewModel<*>> =
withContext(CommonPool) {
val list = ArrayList<BaseViewModel<*>>()
mapMessage(message).let {
if (list.isNotEmpty()) {
it.preview = list.first().preview
}
list.add(it)
}
message.attachments?.forEach {
val attachment = mapAttachment(message, it)
attachment?.let {
list.add(attachment)
}
}
message.urls?.forEach {
val url = mapUrl(message, it)
url?.let {
list.add(url)
}
}
for (i in list.size - 1 downTo 0) {
val next = if (i - 1 < 0) null else list[i - 1]
list[i].nextDownStreamMessage = next
}
if (isBroadcastReplyAvailable(roomViewModel, message)) {
roomsInteractor.getById(currentServer, message.roomId)?.let { chatRoom ->
val replyViewModel = mapMessageReply(message, chatRoom)
list.first().nextDownStreamMessage = replyViewModel
list.add(0, replyViewModel)
}
}
list.dropLast(1).forEach {
it.reactions = emptyList()
}
list.last().reactions = getReactions(message)
list.last().nextDownStreamMessage = null
return@withContext list
}
private fun isBroadcastReplyAvailable(roomViewModel: RoomViewModel, message: Message): Boolean {
val senderUsername = message.sender?.username
return roomViewModel.isRoom && roomViewModel.isBroadcast &&
......@@ -127,8 +188,8 @@ class ViewModelMapper @Inject constructor(
private fun mapMessageReply(message: Message, chatRoom: ChatRoom): MessageReplyViewModel {
val name = message.sender?.name
val roomName = if (settings.useRealName() && name != null) name else message.sender?.username
?: ""
val roomName =
if (settings.useRealName() && name != null) name else message.sender?.username ?: ""
val permalink = messageHelper.createPermalink(message, chatRoom)
return MessageReplyViewModel(
messageId = message.id,
......
......@@ -34,6 +34,7 @@ import chat.rocket.common.model.SimpleUser
import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.model.Subscription
import chat.rocket.core.internal.realtime.createDirectMessage
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.realtime.socket.model.StreamMessage
import chat.rocket.core.internal.realtime.socket.model.Type
......@@ -124,8 +125,19 @@ class ChatRoomsPresenter @Inject constructor(
if (myself?.username == null) {
view.showMessage(R.string.msg_generic_error)
} else {
val id = if (isDirectMessage && !chatRoom.open) {
retryIO("createDirectMessage(${chatRoom.name})") {
client.createDirectMessage(chatRoom.name)
}
val fromTo = mutableListOf(myself.id, chatRoom.id).apply {
sort()
}
fromTo.joinToString("")
} else {
chatRoom.id
}
val isChatRoomOwner = chatRoom.user?.username == myself.username || isDirectMessage
navigator.toChatRoom(chatRoom.id, roomName,
navigator.toChatRoom(id, roomName,
chatRoom.type.toString(), chatRoom.readonly ?: false,
chatRoom.lastSeen ?: -1,
chatRoom.open, isChatRoomOwner)
......@@ -210,7 +222,7 @@ class ChatRoomsPresenter @Inject constructor(
} else {
null
},
name = it.name ?: "",
name = it.username ?: it.name ?: "",
fullName = it.name,
readonly = false,
updatedAt = null,
......@@ -640,4 +652,10 @@ class ChatRoomsPresenter @Inject constructor(
manager.removeRoomsAndSubscriptionsChannel(subscriptionsChannel)
manager.removeActiveUserChannel(activeUserChannel)
}
fun goToChatRoomWithId(chatRoomId: String) {
launchUI(strategy) {
chatRoomsInteractor.getById(currentServer, chatRoomId)?.let { loadChatRoom(it) }
}
}
}
\ No newline at end of file
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.Handler
import android.support.v4.app.Fragment
......@@ -11,7 +9,12 @@ import android.support.v7.util.DiffUtil
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.RadioGroup
import androidx.core.view.isVisible
......@@ -24,7 +27,12 @@ import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.common.model.RoomType
import chat.rocket.core.internal.realtime.socket.model.State
......@@ -36,6 +44,8 @@ import kotlinx.coroutines.experimental.NonCancellable.isActive
import timber.log.Timber
import javax.inject.Inject
private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID"
class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject
lateinit var presenter: ChatRoomsPresenter
......@@ -50,15 +60,30 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
private var listJob: Job? = null
private var sectionedAdapter: SimpleSectionedRecyclerViewAdapter? = null
private var chatRoomId: String? = null
companion object {
fun newInstance() = ChatRoomsFragment()
fun newInstance(chatRoomId: String? = null): ChatRoomsFragment {
return ChatRoomsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomId?.let {
presenter.goToChatRoomWithId(it)
chatRoomId = null
}
}
}
override fun onDestroy() {
......
......@@ -2,10 +2,10 @@ package chat.rocket.android.dagger.module
import chat.rocket.android.chatroom.di.MessageServiceProvider
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.push.FirebaseMessagingService
import chat.rocket.android.push.FirebaseTokenService
import chat.rocket.android.push.GcmListenerService
import chat.rocket.android.push.di.FirebaseMessagingServiceProvider
import chat.rocket.android.push.di.FirebaseTokenServiceProvider
import chat.rocket.android.push.di.GcmListenerServiceProvider
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -14,8 +14,8 @@ import dagger.android.ContributesAndroidInjector
@ContributesAndroidInjector(modules = [FirebaseTokenServiceProvider::class])
abstract fun bindFirebaseTokenService(): FirebaseTokenService
@ContributesAndroidInjector(modules = [GcmListenerServiceProvider::class])
abstract fun bindGcmListenerService(): GcmListenerService
@ContributesAndroidInjector(modules = [FirebaseMessagingServiceProvider::class])
abstract fun bindGcmListenerService(): FirebaseMessagingService
@ContributesAndroidInjector(modules = [MessageServiceProvider::class])
abstract fun bindMessageService(): MessageService
......
......@@ -25,9 +25,9 @@ class FavoriteMessagesPresenter @Inject constructor(
private var offset: Int = 0
/**
* Loads all favorite messages for room. the given room id.
* Loads all favorite messages for the given room id.
*
* @param roomId The id of the room to get its favorite messages.
* @param roomId The id of the room to get favorite messages from.
*/
fun loadFavoriteMessages(roomId: String) {
launchUI(strategy) {
......@@ -35,7 +35,7 @@ class FavoriteMessagesPresenter @Inject constructor(
view.showLoading()
roomsInteractor.getById(serverUrl, roomId)?.let {
val favoriteMessages = client.getFavoriteMessages(roomId, it.type, offset)
val messageList = mapper.map(favoriteMessages.result)
val messageList = mapper.map(favoriteMessages.result, asNotReversed = true)
view.showFavoriteMessages(messageList)
offset += 1 * 30
}.ifNull {
......
......@@ -72,7 +72,7 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
if (favoriteMessages.size > 10) {
if (favoriteMessages.size >= 30) {
recycler_view.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
......
......@@ -94,17 +94,13 @@ class FilesFragment : Fragment(), FilesView {
override fun playMedia(url: String) {
ui {
activity?.let {
PlayerActivity.play(it, url)
}
}
}
override fun openImage(url: String, name: String) {
ui {
activity?.let {
ImageHelper.openImage(it, url, name)
}
ImageHelper.openImage(root_layout.context, url, name)
}
}
......
......@@ -41,9 +41,10 @@ class FileViewModel(
}
private fun getFileUploadDate(): String {
return DateTimeHelper.getDateTime(
DateTimeHelper.getLocalDateTime(genericAttachment.uploadedAt)
)
genericAttachment.uploadedAt?.let {
return DateTimeHelper.getDateTime(DateTimeHelper.getLocalDateTime(it))
}
return ""
}
private fun getFileUrl(): String? {
......
......@@ -53,7 +53,6 @@ object ImageHelper {
)
val toolbar = Toolbar(context).also {
it.inflateMenu(R.menu.image_actions)
it.overflowIcon?.setTint(Color.WHITE)
it.setOnMenuItemClickListener {
return@setOnMenuItemClickListener when (it.itemId) {
R.id.action_save_image -> saveImage(context)
......@@ -109,7 +108,6 @@ object ImageHelper {
.hideStatusBar(false)
.setCustomDraweeControllerBuilder(builder)
.show()
}
private fun saveImage(context: Context): Boolean {
......@@ -166,5 +164,4 @@ object ImageHelper {
)
}
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.app.Activity
import android.content.IntentSender
import android.support.v4.app.FragmentActivity
import com.google.android.gms.auth.api.credentials.*
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.ResolvableApiException
import timber.log.Timber
const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1
const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2
const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3
/**
* This class handles some cases of Google Smart Lock for passwords like the request to retrieve
* credentials, to retrieve sign-in hints and to store the credentials.
*
* See https://developers.google.com/identity/smartlock-passwords/android/overview for futher
* information.
*/
object SmartLockHelper {
/**
* Requests for stored Google Smart Lock credentials.
* Note that in case of exception it will try to start a sign in
* ([REQUEST_CODE_FOR_SIGN_IN_REQUIRED]) or "multiple account"
* ([REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION]) resolution.
*
* @param credentialsClient The credential client.
* @param activity The activity.
* @return null or the [Credential] result.
*/
fun requestStoredCredentials(
credentialsClient: CredentialsClient,
activity: Activity
): Credential? {
var credential: Credential? = null
val credentialRequest = CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.build()
credentialsClient.request(credentialRequest)
.addOnCompleteListener {
when {
it.isSuccessful -> {
credential = it.result.credential
}
it.exception is ResolvableApiException -> {
val resolvableApiException = (it.exception as ResolvableApiException)
if (resolvableApiException.statusCode == CommonStatusCodes.SIGN_IN_REQUIRED) {
provideSignInHint(credentialsClient, activity)
} else {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one. This requires showing UI to
// resolve the read request.
resolveResult(
resolvableApiException,
REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION,
activity
)
}
}
}
}
return credential
}
/**
* Saves a user credential to Google Smart Lock.
* Note that in case of exception it will try to start a save resolution,
* so the activity/fragment should expected for a request code
* ([REQUEST_CODE_FOR_SAVE_RESOLUTION]) on onActivityResult call.
*
* @param credentialsClient The credential client.
* @param activity The activity.
* @param id The user id credential.
* @param password The user password credential.
*/
fun save(
credentialsClient: CredentialsClient,
activity: FragmentActivity,
id: String,
password: String
) {
val credential = Credential.Builder(id)
.setPassword(password)
.build()
credentialsClient.save(credential)
.addOnCompleteListener {
val exception = it.exception
if (exception is ResolvableApiException) {
// Try to resolve the save request. This will prompt the user if
// the credential is new.
try {
exception.startResolutionForResult(
activity,
REQUEST_CODE_FOR_SAVE_RESOLUTION
)
} catch (e: IntentSender.SendIntentException) {
Timber.e("Failed to send resolution. Exception is: $e")
}
}
}
}
private fun provideSignInHint(credentialsClient: CredentialsClient, activity: Activity) {
val hintRequest = HintRequest.Builder()
.setHintPickerConfig(
CredentialPickerConfig.Builder()
.setShowCancelButton(true)
.build()
)
.setEmailAddressIdentifierSupported(true)
.build()
try {
val intent = credentialsClient.getHintPickerIntent(hintRequest)
activity.startIntentSenderForResult(
intent.intentSender,
REQUEST_CODE_FOR_SIGN_IN_REQUIRED,
null,
0,
0,
0,
null
)
} catch (e: IntentSender.SendIntentException) {
Timber.e("Could not start hint picker Intent. Exception is: $e")
}
}
private fun resolveResult(
exception: ResolvableApiException,
requestCode: Int,
activity: Activity
) {
try {
exception.startResolutionForResult(activity, requestCode)
} catch (e: IntentSender.SendIntentException) {
Timber.e("Failed to send resolution. Exception is: $e")
}
}
}
\ No newline at end of file
......@@ -12,9 +12,9 @@ import chat.rocket.android.util.extensions.addFragment
class MainNavigator(internal val activity: MainActivity) {
fun toChatList() {
fun toChatList(chatRoomId: String? = null) {
activity.addFragment("ChatRoomsFragment", R.id.fragment_container) {
ChatRoomsFragment.newInstance()
ChatRoomsFragment.newInstance(chatRoomId)
}
}
......@@ -43,7 +43,7 @@ class MainNavigator(internal val activity: MainActivity) {
}
fun toNewServer(serverUrl: String? = null) {
activity.startActivity(activity.changeServerIntent(serverUrl))
activity.startActivity(activity.changeServerIntent(serverUrl = serverUrl))
activity.finish()
}
......
......@@ -49,7 +49,7 @@ class MainPresenter @Inject constructor(
private val userDataChannel = Channel<Myself>()
fun toChatList() = navigator.toChatList()
fun toChatList(chatRoomId: String? = null) = navigator.toChatList(chatRoomId)
fun toUserProfile() = navigator.toUserProfile()
......@@ -105,13 +105,10 @@ class MainPresenter @Inject constructor(
disconnect()
removeAccountInteractor.remove(currentServer)
tokenRepository.remove(currentServer)
view.disableAutoSignIn()
navigator.toNewServer()
} catch (ex: Exception) {
Timber.d(ex, "Error cleaning up the session...")
}
view.disableAutoSignIn()
navigator.toNewServer()
}
}
......@@ -178,6 +175,7 @@ class MainPresenter @Inject constructor(
if (pushToken != null) {
try {
retryIO("unregisterPushToken") { client.unregisterPushToken(pushToken) }
view.invalidateToken(pushToken)
} catch (ex: Exception) {
Timber.d(ex, "Error unregistering push token")
}
......
......@@ -25,8 +25,5 @@ interface MainView : MessageView, VersionCheckView {
fun closeServerSelection()
/**
* callback to disable auto sign in for google smart lock when the user logs out
*/
fun disableAutoSignIn()
fun invalidateToken(token: String)
}
\ No newline at end of file
......@@ -18,15 +18,15 @@ import chat.rocket.android.main.presentation.MainPresenter
import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.viewmodel.NavHeaderViewModel
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.ui.INTENT_CHAT_ROOM_ID
import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast
import chat.rocket.common.model.UserStatus
import com.google.android.gms.auth.api.Auth
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.messaging.FirebaseMessaging
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
......@@ -40,8 +40,10 @@ import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject
class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupportFragmentInjector,
GoogleApiClient.ConnectionCallbacks {
private const val CURRENT_STATE = "current_state"
class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
HasSupportFragmentInjector {
@Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
@Inject
......@@ -50,73 +52,46 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
lateinit var presenter: MainPresenter
private var isFragmentAdded: Boolean = false
private var expanded = false
private lateinit var googleApiClient: GoogleApiClient
private val headerLayout by lazy { view_navigation.getHeaderView(0) }
private val CURRENT_STATE = "current_state"
private var chatRoomId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buildGoogleApiClient()
launch(CommonPool) {
try {
val token = InstanceID.getInstance(this@MainActivity).getToken(
getString(R.string.gcm_sender_id),
GoogleCloudMessaging.INSTANCE_ID_SCOPE,
null
)
Timber.d("GCM token: $token")
val token = FirebaseInstanceId.getInstance().token
Timber.d("FCM token: $token")
presenter.refreshToken(token)
} catch (ex: Exception) {
Timber.d(ex, "Missing play services...")
}
}
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
presenter.connect()
presenter.loadCurrentInfo()
setupToolbar()
setupNavigationView()
}
override fun onConnected(bundle: Bundle?) {
}
override fun onConnectionSuspended(errorCode: Int) {
}
private fun buildGoogleApiClient() {
googleApiClient = GoogleApiClient.Builder(this)
.enableAutoManage(this, {
Timber.d("ERROR: connection to client failed")
})
.addConnectionCallbacks(this)
.addApi(Auth.CREDENTIALS_API)
.build()
}
override fun onStart() {
super.onStart()
googleApiClient.let {
if (it.isConnected) {
Timber.d("Google api client connected successfully")
}
}
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState?.putBoolean(CURRENT_STATE, isFragmentAdded)
}
override fun disableAutoSignIn() {
googleApiClient.let {
if (it.isConnected) {
Auth.CredentialsApi.disableAutoSignIn(googleApiClient)
}
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
isFragmentAdded = savedInstanceState?.getBoolean(CURRENT_STATE) ?: false
}
override fun onResume() {
super.onResume()
if (!isFragmentAdded) {
presenter.toChatList()
presenter.toChatList(chatRoomId)
isFragmentAdded = true
}
}
......@@ -128,6 +103,12 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
}
}
override fun activityInjector(): AndroidInjector<Activity> = activityDispatchingAndroidInjector
override fun supportFragmentInjector(): AndroidInjector<Fragment> =
fragmentDispatchingAndroidInjector
override fun showUserStatus(userStatus: UserStatus) {
headerLayout.apply {
image_user_status.setImageDrawable(
......@@ -191,38 +172,8 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
.show()
}
private fun setupAccountsList(header: View, accounts: List<Account>) {
accounts_list.layoutManager = LinearLayoutManager(this)
accounts_list.adapter = AccountsAdapter(accounts, object : Selector {
override fun onStatusSelected(userStatus: UserStatus) {
presenter.changeDefaultStatus(userStatus)
}
override fun onAccountSelected(serverUrl: String) {
presenter.changeServer(serverUrl)
}
override fun onAddedAccountSelected() {
presenter.addNewServer()
}
})
header.account_container.setOnClickListener {
header.image_account_expand.rotateBy(180f)
if (expanded) {
accounts_list.fadeOut()
} else {
accounts_list.fadeIn()
}
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 invalidateToken(token: String) {
FirebaseInstanceId.getInstance().deleteToken(token, FirebaseMessaging.INSTANCE_ID_SCOPE)
}
override fun showMessage(resId: Int) = showToast(resId)
......@@ -231,11 +182,6 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun activityInjector(): AndroidInjector<Activity> = activityDispatchingAndroidInjector
override fun supportFragmentInjector(): AndroidInjector<Fragment> =
fragmentDispatchingAndroidInjector
private fun setupToolbar() {
setSupportActionBar(toolbar)
toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp)
......@@ -270,13 +216,37 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp
}
}
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState?.putBoolean(CURRENT_STATE,isFragmentAdded)
private fun setupAccountsList(header: View, accounts: List<Account>) {
accounts_list.layoutManager = LinearLayoutManager(this)
accounts_list.adapter = AccountsAdapter(accounts, object : Selector {
override fun onStatusSelected(userStatus: UserStatus) {
presenter.changeDefaultStatus(userStatus)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
isFragmentAdded = savedInstanceState?.getBoolean(CURRENT_STATE) ?: false
override fun onAccountSelected(serverUrl: String) {
presenter.changeServer(serverUrl)
}
override fun onAddedAccountSelected() {
presenter.addNewServer()
}
})
header.account_container.setOnClickListener {
header.image_account_expand.rotateBy(180f)
if (expanded) {
accounts_list.fadeOut()
} else {
accounts_list.fadeIn()
}
expanded = !expanded
}
header.image_avatar.setOnClickListener {
view_navigation.menu.findItem(R.id.action_profile).isChecked = true
presenter.toUserProfile()
drawer_layout.closeDrawer(Gravity.START)
}
}
}
\ No newline at end of file
......@@ -26,7 +26,7 @@ class PinnedMessagesPresenter @Inject constructor(
private var offset: Int = 0
/**
* Load all pinned messages for the given room id.
* Loads all pinned messages for the given room id.
*
* @param roomId The id of the room to get pinned messages from.
*/
......@@ -36,8 +36,7 @@ class PinnedMessagesPresenter @Inject constructor(
view.showLoading()
roomsInteractor.getById(serverUrl, roomId)?.let {
val pinnedMessages = client.getPinnedMessages(roomId, it.type, offset)
val messageList =
mapper.map(pinnedMessages.result.filterNot { it.isSystemMessage() })
val messageList = mapper.map(pinnedMessages.result, asNotReversed = true)
view.showPinnedMessages(messageList)
offset += 1 * 30
}.ifNull {
......
......@@ -74,7 +74,7 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recycler_view_pinned.layoutManager = linearLayoutManager
recycler_view_pinned.itemAnimator = DefaultItemAnimator()
if (pinnedMessages.size > 10) {
if (pinnedMessages.size >= 30) {
recycler_view_pinned.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
......
......@@ -58,7 +58,8 @@ class ProfilePresenter @Inject constructor(private val view: ProfileView,
if(avatarUrl!="") {
retryIO { client.setAvatar(avatarUrl) }
}
val user = retryIO { client.updateProfile(myselfId, email, name, username) }
val user = retryIO { client.updateProfile(
userId = myselfId, email = email, name = name, username = username) }
view.showProfileUpdateSuccessfullyMessage()
loadUserProfile()
} catch (exception: RocketChatException) {
......
package chat.rocket.android.push
import android.os.Bundle
import com.google.android.gms.gcm.GcmListenerService
import androidx.core.os.bundleOf
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dagger.android.AndroidInjection
import javax.inject.Inject
class GcmListenerService : GcmListenerService() {
class FirebaseMessagingService : FirebaseMessagingService() {
@Inject
lateinit var pushManager: PushManager
......@@ -15,9 +16,9 @@ class GcmListenerService : GcmListenerService() {
AndroidInjection.inject(this)
}
override fun onMessageReceived(from: String?, data: Bundle?) {
data?.let {
pushManager.handle(data)
override fun onMessageReceived(message: RemoteMessage) {
message.data?.let {
pushManager.handle(bundleOf(*(it.map { Pair(it.key, it.value) }).toTypedArray()))
}
}
}
\ No newline at end of file
package chat.rocket.android.push
import chat.rocket.android.R
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.registerPushToken
import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.iid.FirebaseInstanceIdService
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.launch
......@@ -31,21 +29,18 @@ class FirebaseTokenService : FirebaseInstanceIdService() {
}
override fun onTokenRefresh() {
//TODO: We need to use the Cordova Project gcm_sender_id since it's the one configured on RC
// default push gateway. We should register this project's own project sender id into it.
try {
val gcmToken = InstanceID.getInstance(this)
.getToken(getString(R.string.gcm_sender_id), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null)
val fcmToken = FirebaseInstanceId.getInstance().token
val currentServer = getCurrentServerInteractor.get()
val client = currentServer?.let { factory.create(currentServer) }
gcmToken?.let {
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, gcmToken)
fcmToken?.let {
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, fcmToken)
client?.let {
launch {
try {
Timber.d("Registering push token: $gcmToken for ${client.url}")
retryIO("register push token") { client.registerPushToken(gcmToken) }
Timber.d("Registering push token: $fcmToken for ${client.url}")
retryIO("register push token") { client.registerPushToken(fcmToken) }
} catch (ex: RocketChatException) {
Timber.e(ex, "Error registering push token")
}
......@@ -53,7 +48,7 @@ class FirebaseTokenService : FirebaseInstanceIdService() {
}
}
} catch (ex: Exception) {
Timber.d(ex, "Error refreshing Firebase TOKEN")
Timber.e(ex, "Error refreshing Firebase TOKEN")
}
}
}
\ No newline at end of file
......@@ -48,7 +48,8 @@ class PushManager @Inject constructor(
private val getSettingsInteractor: GetSettingsInteractor,
private val context: Context
) {
private val randomizer = Random()
private val random = Random()
/**
* Handles a receiving push by creating and displaying an appropriate notification based
......@@ -59,7 +60,7 @@ class PushManager @Inject constructor(
val message = data["message"] as String?
val ejson = data["ejson"] as String?
val title = data["title"] as String?
val notId = data["notId"] as String? ?: randomizer.nextInt().toString()
val notId = data["notId"] as String? ?: random.nextInt().toString()
val image = data["image"] as String?
val style = data["style"] as String?
val summaryText = data["summaryText"] as String?
......@@ -67,9 +68,13 @@ class PushManager @Inject constructor(
try {
val adapter = moshi.adapter<PushInfo>(PushInfo::class.java)
val info = adapter.fromJson(ejson)
val pushMessage = PushMessage(title!!, message!!, info!!, image, count, notId, summaryText, style)
val pushMessage = if (ejson != null) {
val info = adapter.fromJson(ejson)
PushMessage(title!!, message!!, info!!, image, count, notId, summaryText, style)
} else {
PushMessage(title!!, message!!, PushInfo.EMPTY, image, count, notId, summaryText, style)
}
Timber.d("Received push message: $pushMessage")
......@@ -82,13 +87,17 @@ class PushManager @Inject constructor(
@SuppressLint("NewApi")
suspend fun showNotification(pushMessage: PushMessage) {
if (!hasAccount(pushMessage.info.host)) {
Timber.d("ignoring push message: $pushMessage")
val notId = pushMessage.notificationId.toInt()
val host = pushMessage.info.host
if (!hasAccount(host)) {
createSingleNotification(pushMessage)?.let {
NotificationManagerCompat.from(context).notify(notId, it)
}
Timber.d("ignoring push message: $pushMessage (maybe a test notification?)")
return
}
val notId = pushMessage.notificationId.toInt()
val host = pushMessage.info.host
val groupTuple = getGroupForHost(host)
groupTuple.second.incrementAndGet()
......@@ -103,7 +112,7 @@ class PushManager @Inject constructor(
val pushMessageList = groupedPushes.hostToPushMessageList[host]
notification?.let {
manager.notify(notId, notification)
manager.notify(notId, it)
}
pushMessageList?.let {
......@@ -182,7 +191,7 @@ class PushManager @Inject constructor(
if (style == null || "inbox" == style) {
val pushMessageList = groupedPushes.hostToPushMessageList.get(host)
pushMessageList?.let {
if (pushMessageList != null) {
val userMessages = pushMessageList.filter {
it.notificationId == pushMessage.notificationId
}
......@@ -206,6 +215,12 @@ class PushManager @Inject constructor(
.bigText(message.fromHtml())
builder.setStyle(bigTextStyle)
}
} else {
// We don't know which kind of push is this - maybe a test push, so just show it
val bigTextStyle = NotificationCompat.BigTextStyle()
.bigText(message.fromHtml())
builder.setStyle(bigTextStyle)
return builder.build()
}
} else {
val bigTextStyle = NotificationCompat.BigTextStyle()
......@@ -213,8 +228,7 @@ class PushManager @Inject constructor(
builder.setStyle(bigTextStyle)
}
return builder.addReplyAction(pushMessage)
.build()
return builder.addReplyAction(pushMessage).build()
}
}
......@@ -235,13 +249,27 @@ class PushManager @Inject constructor(
.setContentIntent(contentIntent)
.setMessageNotification()
if (host.isEmpty()) {
builder.setContentIntent(deleteIntent)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(host, host, NotificationManager.IMPORTANCE_HIGH)
val channelId: String
val channelName: String
if (host.isEmpty()) {
channelName = "Test Notification"
channelId = "test-channel"
} else {
channelName = host
channelId = host
}
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.enableLights(false)
channel.enableVibration(true)
channel.setShowBadge(true)
manager.createNotificationChannel(channel)
builder.setChannelId(channelId)
}
//TODO: Get Site_Name PublicSetting from cache
......@@ -271,12 +299,12 @@ class PushManager @Inject constructor(
}
private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent {
val notificationIntent = context.changeServerIntent(pushMessage.info.host)
val notificationIntent = context.changeServerIntent(pushMessage.info.host, chatRoomId = pushMessage.info.roomId)
// TODO - add support to go directly to the chatroom
/*if (!grouped) {
notificationIntent.putExtra(EXTRA_ROOM_ID, pushMessage.info.roomId)
}*/
return PendingIntent.getActivity(context, randomizer.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getActivity(context, random.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// CharSequence extensions
......@@ -318,14 +346,14 @@ class PushManager @Inject constructor(
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PendingIntent.getBroadcast(
context,
randomizer.nextInt(),
random.nextInt(),
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getActivity(
context,
randomizer.nextInt(),
random.nextInt(),
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
......@@ -359,6 +387,7 @@ data class PushMessage(
val summaryText: String? = null,
val style: String? = null
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
......@@ -438,6 +467,9 @@ data class PushInfo @KotshiConstructor constructor(
}
companion object CREATOR : Parcelable.Creator<PushInfo> {
val EMPTY = PushInfo(hostname = "", roomId = "", type = RoomType.CHANNEL, name = "",
sender = null)
override fun createFromParcel(parcel: Parcel): PushInfo {
return PushInfo(parcel)
}
......
package chat.rocket.android.push.di
import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.push.GcmListenerService
import chat.rocket.android.push.FirebaseMessagingService
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class GcmListenerServiceProvider {
@Module abstract class FirebaseMessagingServiceProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideGcmListenerService(): GcmListenerService
abstract fun provideFirebaseMessagingService(): FirebaseMessagingService
}
\ No newline at end of file
......@@ -3,16 +3,16 @@ package chat.rocket.android.server.infraestructure
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.User
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.realtime.subscribeSubscriptions
import chat.rocket.core.internal.realtime.subscribeRooms
import chat.rocket.core.internal.realtime.subscribeUserData
import chat.rocket.core.internal.realtime.subscribeActiveUsers
import chat.rocket.core.internal.realtime.subscribeRoomMessages
import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.realtime.socket.connect
import chat.rocket.core.internal.realtime.socket.disconnect
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.realtime.socket.model.StreamMessage
import chat.rocket.core.internal.realtime.subscribeActiveUsers
import chat.rocket.core.internal.realtime.subscribeRoomMessages
import chat.rocket.core.internal.realtime.subscribeRooms
import chat.rocket.core.internal.realtime.subscribeSubscriptions
import chat.rocket.core.internal.realtime.subscribeUserData
import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself
......
......@@ -4,6 +4,7 @@ import android.content.Intent
import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.ui.ChangeServerActivity
import chat.rocket.android.server.ui.INTENT_CHAT_ROOM_ID
class ChangeServerNavigator (internal val activity: ChangeServerActivity) {
fun toServerScreen() {
......@@ -11,8 +12,10 @@ class ChangeServerNavigator (internal val activity: ChangeServerActivity) {
activity.finish()
}
fun toChatRooms() {
activity.startActivity(Intent(activity, MainActivity::class.java))
fun toChatRooms(chatRoomId: String? = null) {
activity.startActivity(Intent(activity, MainActivity::class.java).also {
it.putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
})
activity.finish()
}
......
......@@ -21,7 +21,7 @@ class ChangeServerPresenter @Inject constructor(
private val localRepository: LocalRepository,
private val connectionManager: ConnectionManagerFactory
) {
fun loadServer(newUrl: String?) {
fun loadServer(newUrl: String?, chatRoomId: String? = null) {
launchUI(strategy) {
view.showProgress()
var url = newUrl
......@@ -56,7 +56,7 @@ class ChangeServerPresenter @Inject constructor(
saveCurrentServerInteractor.save(serverUrl)
view.hideProgress()
navigator.toChatRooms()
navigator.toChatRooms(chatRoomId)
}.ifNull {
view.hideProgress()
navigator.toServerScreen()
......
......@@ -21,7 +21,8 @@ class ChangeServerActivity : AppCompatActivity(), ChangeServerView {
super.onCreate(savedInstanceState)
val serverUrl: String? = intent.getStringExtra(INTENT_SERVER_URL)
presenter.loadServer(serverUrl)
val chatRoomId: String? = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
presenter.loadServer(serverUrl, chatRoomId)
}
override fun showInvalidCredentials() {
......@@ -40,11 +41,13 @@ class ChangeServerActivity : AppCompatActivity(), ChangeServerView {
private const val INTENT_SERVER_URL = "INTENT_SERVER_URL"
private const val INTENT_CHAT_ROOM_NAME = "INTENT_CHAT_ROOM_NAME"
private const val INTENT_CHAT_ROOM_TYPE = "INTENT_CHAT_ROOM_TYPE"
const val INTENT_CHAT_ROOM_ID = "INTENT_CHAT_ROOM_ID"
fun Context.changeServerIntent(serverUrl: String? = null): Intent {
fun Context.changeServerIntent(serverUrl: String? = null, chatRoomId: String? = ""): Intent {
return Intent(this, ChangeServerActivity::class.java).apply {
serverUrl?.let { url ->
putExtra(INTENT_SERVER_URL, url)
putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
}
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
......
......@@ -32,6 +32,18 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_headline" />
<ImageView
android:id="@+id/image_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_vpn_key_black_24dp"
android:tint="@color/colorDrawableTintGrey"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/text_username_or_email"
app:layout_constraintEnd_toEndOf="@+id/text_username_or_email"
app:layout_constraintTop_toTopOf="@+id/text_username_or_email" />
<EditText
android:id="@+id/text_password"
style="@style/Authentication.EditText"
......
......@@ -2,6 +2,7 @@
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".files.ui.FilesFragment">
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- This is the Cordova GCM sender id-->
<string name="gcm_sender_id" translatable="false">673693445664</string>
</resources>
\ No newline at end of file
File added
......@@ -14,7 +14,8 @@ ext {
androidKtx : '0.3',
dagger : '2.14.1',
exoPlayer : '2.6.0',
playServices : '11.8.0',
playServices : '15.0.0',
firebase : '15.0.0',
room : '1.0.0',
lifecycle : '1.1.1',
rxKotlin : '2.2.0',
......@@ -63,7 +64,7 @@ ext {
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
playServicesGcm : "com.google.android.gms:play-services-gcm:${versions.playServices}",
fcm : "com.google.firebase:firebase-messaging:${versions.firebase}",
playServicesAuth : "com.google.android.gms:play-services-auth:${versions.playServices}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
......@@ -102,19 +103,19 @@ ext {
aVLoadingIndicatorView: "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
// For testing
junit : "junit:junit:$versions.junit",
espressoCore : "com.android.support.test.espresso:espresso-core:${versions.espresso}",
espressoIntents : "com.android.support.test.espresso:espresso-intents:${versions.espresso}",
roomTest : "android.arch.persistence.room:testing:${versions.room}",
truth : "com.google.truth:truth:$versions.truth",
//For the wear app
wearable : "com.google.android.support:wearable:${versions.wear}",
playServicesWearable : "com.google.android.gms:play-services-wearable:${versions.playServicesWearable}",
percentLayout : "com.android.support:percent:${versions.supportWearable}",
supportWearable : "com.android.support:support-v4:${versions.supportWearable}",
wearableRecyclerView : "com.android.support:recyclerview-v7:${versions.supportWearable}",
wearSupport : "com.android.support:wear:${versions.supportWearable}"
wearSupport : "com.android.support:wear:${versions.supportWearable}",
// For testing
junit : "junit:junit:$versions.junit",
espressoCore : "com.android.support.test.espresso:espresso-core:${versions.espresso}",
espressoIntents : "com.android.support.test.espresso:espresso-intents:${versions.espresso}",
roomTest : "android.arch.persistence.room:testing:${versions.room}",
truth : "com.google.truth:truth:$versions.truth"
]
}
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