diff --git a/app/build.gradle b/app/build.gradle index bf152facd5..52e14139a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "chat.rocket.android" minSdkVersion 21 targetSdkVersion versions.targetSdk - versionCode 2021 - versionName "2.2.0" + 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,8 @@ dependencies { kapt libraries.daggerProcessor kapt libraries.daggerAndroidApt - implementation libraries.playServicesGcm + implementation libraries.fcm + implementation libraries.playServicesAuth implementation libraries.room kapt libraries.roomProcessor diff --git a/app/google-services.json b/app/google-services.json index 7a936ea18e..a818e52b98 100644 --- a/app/google-services.json +++ b/app/google-services.json @@ -1,27 +1,51 @@ { "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,218 @@ }, { "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": "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: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": "AIzaSyDc7VYUdU6kRkoRTToiCn1rh-W0wJvhLWk" + "current_key": "AIzaSyDIkZj1TRz8TmhnMswDwVY5OnWuzFK3rxg" } ], "services": { @@ -60,8 +282,20 @@ "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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 11306f2ce1..37112f7dc6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,25 +3,17 @@ package="chat.rocket.android"> - - - - - - + + @@ -90,18 +84,6 @@ android:name=".settings.about.ui.AboutActivity" android:theme="@style/AppTheme" /> - - - - - - - - - + + android:name=".push.FirebaseMessagingService" + android:enabled="true" + android:exported="true"> - + + >): List> { + private fun getCustomOauthServices(listMap: List>): List> { return listMap.filter { map -> map["custom"] == true } } diff --git a/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginView.kt b/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginView.kt index 98a2d6be1c..6bf56c31ae 100644 --- a/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginView.kt +++ b/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginView.kt @@ -2,6 +2,7 @@ package chat.rocket.android.authentication.login.presentation import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.MessageView +import com.google.android.gms.auth.api.credentials.Credential interface LoginView : LoadingView, MessageView { @@ -223,4 +224,9 @@ interface LoginView : LoadingView, MessageView { * Alerts the user about a wrong inputted password. */ fun alertWrongPassword() + + /** + * Saves Google Smart Lock credentials. + */ + fun saveSmartLockCredentials(id: String, password: String) } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/login/ui/LoginFragment.kt b/app/src/main/java/chat/rocket/android/authentication/login/ui/LoginFragment.kt index b71a1780c6..3d4675f229 100644 --- a/app/src/main/java/chat/rocket/android/authentication/login/ui/LoginFragment.kt +++ b/app/src/main/java/chat/rocket/android/authentication/login/ui/LoginFragment.kt @@ -22,8 +22,7 @@ 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.cas.ui.INTENT_CAS_TOKEN import chat.rocket.android.webview.cas.ui.casWebViewIntent @@ -31,12 +30,13 @@ 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.credentials.* import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.fragment_authentication_log_in.* import javax.inject.Inject -internal const val REQUEST_CODE_FOR_CAS = 1 -internal const val REQUEST_CODE_FOR_OAUTH = 2 +internal const val REQUEST_CODE_FOR_CAS = 4 +internal const val REQUEST_CODE_FOR_OAUTH = 5 class LoginFragment : Fragment(), LoginView { @Inject @@ -47,6 +47,7 @@ class LoginFragment : Fragment(), LoginView { } private var isGlobalLayoutListenerSetUp = false private var deepLinkInfo: LoginDeepLinkInfo? = null + private val credentialsClient by lazy { Credentials.getClient(requireActivity()) } companion object { private const val DEEP_LINK_INFO = "DeepLinkInfo" @@ -95,21 +96,42 @@ class LoginFragment : Fragment(), LoginView { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK) { - if (requestCode == REQUEST_CODE_FOR_CAS) { - data?.apply { - presenter.authenticateWithCas(getStringExtra(INTENT_CAS_TOKEN)) - } - } else if (requestCode == REQUEST_CODE_FOR_OAUTH) { - data?.apply { - presenter.authenticateWithOauth( - getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN), - getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET) - ) + if (data != null) { + when (requestCode) { + 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_CAS_TOKEN)) + } + REQUEST_CODE_FOR_OAUTH -> { + presenter.authenticateWithOauth( + data.getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN), + data.getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET) + ) + } } } } } + override fun onResume() { + super.onResume() + image_key.setOnClickListener { + requestStoredCredentials() + image_key.isVisible = false + } + } + private fun tintEditTextDrawableStart() { ui { val personDrawable = @@ -126,6 +148,24 @@ class LoginFragment : Fragment(), LoginView { } } + 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 @@ -158,6 +198,7 @@ class LoginFragment : Fragment(), LoginView { ui { text_username_or_email.isVisible = true text_password.isVisible = true + image_key.isVisible = true } } diff --git a/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt index c05e87b4a7..8350b8dd3b 100644 --- a/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt @@ -32,6 +32,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView, } fun connect(server: String) { + //code that leads to login screen (smart lock will be implemented after this) connectToServer(server) { navigator.toLogin() } @@ -64,6 +65,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView, } fun deepLink(deepLinkInfo: LoginDeepLinkInfo) { + //code that leads to login screen (smart lock will be implemented after this) connectToServer(deepLinkInfo.url) { navigator.toLogin(deepLinkInfo) } diff --git a/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt index 183bda5144..38df71b42c 100644 --- a/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt @@ -6,12 +6,7 @@ import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.infraestructure.RocketChatClientFactory -import chat.rocket.android.util.extensions.avatarUrl -import chat.rocket.android.util.extensions.launchUI -import chat.rocket.android.util.extensions.privacyPolicyUrl -import chat.rocket.android.util.extensions.registerPushToken -import chat.rocket.android.util.extensions.serverLogoUrl -import chat.rocket.android.util.extensions.termsOfServiceUrl +import chat.rocket.android.util.extensions.* import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException import chat.rocket.common.util.ifNull @@ -22,15 +17,17 @@ import chat.rocket.core.internal.rest.signup import chat.rocket.core.model.Myself import javax.inject.Inject -class SignupPresenter @Inject constructor(private val view: SignupView, - private val strategy: CancelStrategy, - private val navigator: AuthenticationNavigator, - private val localRepository: LocalRepository, - private val serverInteractor: GetCurrentServerInteractor, - private val factory: RocketChatClientFactory, - private val saveAccountInteractor: SaveAccountInteractor, - private val getAccountsInteractor: GetAccountsInteractor, - settingsInteractor: GetSettingsInteractor) { +class SignupPresenter @Inject constructor( + private val view: SignupView, + private val strategy: CancelStrategy, + private val navigator: AuthenticationNavigator, + private val localRepository: LocalRepository, + private val serverInteractor: GetCurrentServerInteractor, + private val factory: RocketChatClientFactory, + private val saveAccountInteractor: SaveAccountInteractor, + private val getAccountsInteractor: GetAccountsInteractor, + settingsInteractor: GetSettingsInteractor +) { private val currentServer = serverInteractor.get()!! private val client: RocketChatClient = factory.create(currentServer) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) @@ -66,6 +63,7 @@ class SignupPresenter @Inject constructor(private val view: SignupView, localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) saveAccount(me) registerPushToken() + view.saveSmartLockCredentials(username, password) navigator.toChatList() } catch (exception: RocketChatException) { exception.message?.let { diff --git a/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupView.kt b/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupView.kt index 3c0d3d50f0..5feb798f46 100644 --- a/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupView.kt +++ b/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupView.kt @@ -2,6 +2,7 @@ package chat.rocket.android.authentication.signup.presentation import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.MessageView +import com.google.android.gms.auth.api.credentials.Credential interface SignupView : LoadingView, MessageView { @@ -24,4 +25,9 @@ interface SignupView : LoadingView, MessageView { * Alerts the user about a blank email. */ fun alertBlankEmail() + + /** + * Saves Google Smart Lock credentials. + */ + fun saveSmartLockCredentials(id: String, password: String) } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/signup/ui/SignupFragment.kt b/app/src/main/java/chat/rocket/android/authentication/signup/ui/SignupFragment.kt index b45e595f78..5e57d860da 100644 --- a/app/src/main/java/chat/rocket/android/authentication/signup/ui/SignupFragment.kt +++ b/app/src/main/java/chat/rocket/android/authentication/signup/ui/SignupFragment.kt @@ -1,24 +1,34 @@ package chat.rocket.android.authentication.signup.ui import DrawableHelper +import android.app.Activity +import android.content.Intent import android.os.Build import android.os.Bundle import android.support.v4.app.Fragment import android.text.style.ClickableSpan -import android.view.* -import android.widget.Toast +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver import chat.rocket.android.R +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.credentials.Credentials import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.fragment_authentication_sign_up.* import javax.inject.Inject +internal const val SAVE_CREDENTIALS = 1 + class SignupFragment : Fragment(), SignupView { - @Inject lateinit var presenter: SignupPresenter + @Inject + lateinit var presenter: SignupPresenter private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { if (KeyboardHelper.isSoftKeyboardShown(relative_layout.rootView)) { bottom_container.setVisible(false) @@ -40,7 +50,11 @@ class SignupFragment : Fragment(), SignupView { AndroidSupportInjection.inject(this) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_sign_up, container, false) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.fragment_authentication_sign_up, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -54,7 +68,12 @@ class SignupFragment : Fragment(), SignupView { setUpNewUserAgreementListener() button_sign_up.setOnClickListener { - presenter.signup(text_username.textContent, text_username.textContent, text_password.textContent, text_email.textContent) + presenter.signup( + text_username.textContent, + text_username.textContent, + text_password.textContent, + text_email.textContent + ) } } @@ -95,6 +114,16 @@ class SignupFragment : Fragment(), SignupView { } } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK) { + if (data != null) { + if (requestCode == SAVE_CREDENTIALS) { + showMessage(getString(message_credentials_saved_successfully)) + } + } + } + } + override fun showLoading() { ui { enableUserInput(false) @@ -125,9 +154,16 @@ 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 = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, it) + val personDrawable = + DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, it) val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, it) val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, it) val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, it) @@ -135,14 +171,22 @@ class SignupFragment : Fragment(), SignupView { val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable) DrawableHelper.wrapDrawables(drawables) DrawableHelper.tintDrawables(drawables, it, R.color.colorDrawableTintGrey) - DrawableHelper.compoundDrawables(arrayOf(text_name, text_username, text_password, text_email), drawables) + DrawableHelper.compoundDrawables( + arrayOf( + text_name, + text_username, + text_password, + text_email + ), drawables + ) } } private fun setUpNewUserAgreementListener() { val termsOfService = getString(R.string.action_terms_of_service) val privacyPolicy = getString(R.string.action_privacy_policy) - val newUserAgreement = String.format(getString(R.string.msg_new_user_agreement), termsOfService, privacyPolicy) + val newUserAgreement = + String.format(getString(R.string.msg_new_user_agreement), termsOfService, privacyPolicy) text_new_user_agreement.text = newUserAgreement @@ -158,7 +202,11 @@ class SignupFragment : Fragment(), SignupView { } } - TextHelper.addLink(text_new_user_agreement, arrayOf(termsOfService, privacyPolicy), arrayOf(termsOfServiceListener, privacyPolicyListener)) + TextHelper.addLink( + text_new_user_agreement, + arrayOf(termsOfService, privacyPolicy), + arrayOf(termsOfServiceListener, privacyPolicyListener) + ) } private fun enableUserInput(value: Boolean) { diff --git a/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt b/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt index 5c9d86f9de..b4290d5070 100644 --- a/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt +++ b/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt @@ -21,8 +21,10 @@ import kotlinx.coroutines.experimental.launch import javax.inject.Inject class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { - @Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector - @Inject lateinit var presenter: AuthenticationPresenter + @Inject + lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector + @Inject + lateinit var presenter: AuthenticationPresenter val job = Job() override fun onCreate(savedInstanceState: Bundle?) { @@ -43,6 +45,14 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { } } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) + if (currentFragment != null) { + currentFragment.onActivityResult(requestCode, resultCode, data) + } + } + override fun onDestroy() { job.cancel() super.onDestroy() diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt index 56b1bdef4f..7b93b78a27 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt @@ -5,21 +5,7 @@ import android.view.MenuItem import android.view.ViewGroup import chat.rocket.android.R import chat.rocket.android.chatroom.presentation.ChatRoomPresenter -import chat.rocket.android.chatroom.ui.chatRoomIntent -import chat.rocket.android.chatroom.viewmodel.AudioAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.AuthorAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.BaseFileAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.BaseViewModel -import chat.rocket.android.chatroom.viewmodel.ColorAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.GenericFileAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.ImageAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.MessageAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.MessageReplyViewModel -import chat.rocket.android.chatroom.viewmodel.MessageViewModel -import chat.rocket.android.chatroom.viewmodel.UrlPreviewViewModel -import chat.rocket.android.chatroom.viewmodel.VideoAttachmentViewModel -import chat.rocket.android.chatroom.viewmodel.toViewType -import chat.rocket.android.main.presentation.MainNavigator +import chat.rocket.android.chatroom.viewmodel.* import chat.rocket.android.util.extensions.inflate import chat.rocket.android.widget.emoji.EmojiReactionListener import chat.rocket.core.model.Message @@ -28,13 +14,12 @@ import timber.log.Timber import java.security.InvalidParameterException class ChatRoomAdapter( - private val roomType: String, - private val roomName: String, - private val presenter: ChatRoomPresenter?, + private val roomType: String? = null, + private val roomName: String? = null, + private val presenter: ChatRoomPresenter? = null, private val enableActions: Boolean = true, private val reactionListener: EmojiReactionListener? = null ) : RecyclerView.Adapter>() { - private val dataSet = ArrayList>() init { @@ -178,7 +163,7 @@ class ChatRoomAdapter( } fun updateItem(message: BaseViewModel<*>) { - var index = dataSet.indexOfLast { it.messageId == message.messageId } + val index = dataSet.indexOfLast { it.messageId == message.messageId } val indexOfNext = dataSet.indexOfFirst { it.messageId == message.messageId } Timber.d("index: $index") if (index > -1) { @@ -219,10 +204,14 @@ class ChatRoomAdapter( message.apply { when (item.itemId) { R.id.action_message_reply -> { - presenter?.citeMessage(roomName, roomType, id, true) + if (roomName != null && roomType != null) { + presenter?.citeMessage(roomName, roomType, id, true) + } } R.id.action_message_quote -> { - presenter?.citeMessage(roomName, roomType, id, false) + if (roomName != null && roomType != null) { + presenter?.citeMessage(roomName, roomType, id, false) + } } R.id.action_message_copy -> { presenter?.copyMessage(id) diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/GenericFileAttachmentViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/GenericFileAttachmentViewHolder.kt index 6c17c0822b..a3703c8d86 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/GenericFileAttachmentViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/GenericFileAttachmentViewHolder.kt @@ -3,6 +3,7 @@ package chat.rocket.android.chatroom.adapter import android.content.Intent import android.net.Uri import android.view.View +import androidx.core.net.toUri import chat.rocket.android.chatroom.viewmodel.GenericFileAttachmentViewModel import chat.rocket.android.util.extensions.content import chat.rocket.android.widget.emoji.EmojiReactionListener @@ -25,7 +26,7 @@ class GenericFileAttachmentViewHolder(itemView: View, text_file_name.content = data.attachmentTitle text_file_name.setOnClickListener { - it.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(data.attachmentUrl))) + it.context.startActivity(Intent(Intent.ACTION_VIEW, data.attachmentUrl.toUri())) } } } diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/ImageAttachmentViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/ImageAttachmentViewHolder.kt index cd8226f016..a7f9c67544 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/ImageAttachmentViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/ImageAttachmentViewHolder.kt @@ -1,48 +1,17 @@ package chat.rocket.android.chatroom.adapter -import android.Manifest -import android.app.Activity -import android.graphics.Color -import android.graphics.Typeface -import android.media.MediaScannerConnection -import android.net.Uri -import android.os.Environment -import android.support.design.widget.AppBarLayout -import android.support.v7.widget.Toolbar -import android.text.TextUtils -import android.util.TypedValue -import android.view.ContextThemeWrapper -import android.view.Gravity import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import android.widget.Toast -import androidx.core.view.setPadding -import chat.rocket.android.R import chat.rocket.android.chatroom.viewmodel.ImageAttachmentViewModel -import chat.rocket.android.helper.AndroidPermissionsHelper +import chat.rocket.android.helper.ImageHelper import chat.rocket.android.widget.emoji.EmojiReactionListener -import com.facebook.binaryresource.FileBinaryResource -import com.facebook.cache.common.CacheKey import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imageformat.ImageFormatChecker -import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory -import com.facebook.imagepipeline.core.ImagePipelineFactory -import com.facebook.imagepipeline.request.ImageRequest -import com.facebook.imagepipeline.request.ImageRequestBuilder -import com.stfalcon.frescoimageviewer.ImageViewer import kotlinx.android.synthetic.main.message_attachment.view.* -import timber.log.Timber -import java.io.File - class ImageAttachmentViewHolder( itemView: View, listener: ActionsListener, reactionListener: EmojiReactionListener? = null ) : BaseViewHolder(itemView, listener, reactionListener) { - private var cacheKey: CacheKey? = null init { with(itemView) { @@ -59,138 +28,13 @@ class ImageAttachmentViewHolder( }.build() image_attachment.controller = controller file_name.text = data.attachmentTitle - image_attachment.setOnClickListener { view -> - // TODO - implement a proper image viewer with a proper Transition - // TODO - We should definitely write our own ImageViewer - var imageViewer: ImageViewer? = null - val request = - ImageRequestBuilder.newBuilderWithSource(Uri.parse(data.attachmentUrl)) - .setLowestPermittedRequestLevel(ImageRequest.RequestLevel.DISK_CACHE) - .build() - - cacheKey = DefaultCacheKeyFactory.getInstance() - .getEncodedCacheKey(request, null) - val pad = context.resources - .getDimensionPixelSize(R.dimen.viewer_toolbar_padding) - val lparams = AppBarLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - 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() - else -> super.onMenuItemClick(it) - } - } - - val titleSize = context.resources - .getDimensionPixelSize(R.dimen.viewer_toolbar_title) - val titleTextView = TextView(context).also { - it.text = data.attachmentTitle - it.setTextColor(Color.WHITE) - it.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize.toFloat()) - it.ellipsize = TextUtils.TruncateAt.END - it.setSingleLine() - it.typeface = Typeface.DEFAULT_BOLD - it.setPadding(pad) - } - - val backArrowView = ImageView(context).also { - it.setImageResource(R.drawable.ic_arrow_back_white_24dp) - it.setOnClickListener { imageViewer?.onDismiss() } - it.setPadding(0, pad, pad, pad) - } - - val layoutParams = AppBarLayout.LayoutParams( - AppBarLayout.LayoutParams.WRAP_CONTENT, - AppBarLayout.LayoutParams.WRAP_CONTENT - ) - - it.addView(backArrowView, layoutParams) - it.addView(titleTextView, layoutParams) - } - - val appBarLayout = AppBarLayout(context).also { - it.layoutParams = lparams - it.setBackgroundColor(Color.BLACK) - it.addView( - toolbar, AppBarLayout.LayoutParams( - AppBarLayout.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - ) - } - - val builder = ImageViewer.createPipelineDraweeControllerBuilder() - .setImageRequest(request) - .setAutoPlayAnimations(true) - imageViewer = ImageViewer.Builder(view.context, listOf(data.attachmentUrl)) - .setOverlayView(appBarLayout) - .setStartPosition(0) - .hideStatusBar(false) - .setCustomDraweeControllerBuilder(builder) - .show() - } - } - } - - private fun saveImage(): Boolean { - if (!canWriteToExternalStorage()) { - checkWritingPermission() - return false - } - if (ImagePipelineFactory.getInstance().mainFileCache.hasKey(cacheKey)) { - val context = itemView.context - val resource = ImagePipelineFactory.getInstance().mainFileCache.getResource(cacheKey) - val cachedFile = (resource as FileBinaryResource).file - val imageFormat = ImageFormatChecker.getImageFormat(resource.openStream()) - val imageDir = "${Environment.DIRECTORY_PICTURES}/Rocket.Chat Images/" - val imagePath = Environment.getExternalStoragePublicDirectory(imageDir) - val imageFile = - File(imagePath, "${cachedFile.nameWithoutExtension}.${imageFormat.fileExtension}") - imagePath.mkdirs() - imageFile.createNewFile() - try { - cachedFile.copyTo(imageFile, true) - MediaScannerConnection.scanFile( + image_attachment.setOnClickListener { + ImageHelper.openImage( context, - arrayOf(imageFile.absolutePath), - null - ) { path, uri -> - Timber.i("Scanned $path:") - Timber.i("-> uri=$uri") - } - } catch (ex: Exception) { - Timber.e(ex) - val message = context.getString(R.string.msg_image_saved_failed) - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } finally { - val message = context.getString(R.string.msg_image_saved_successfully) - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + data.attachmentUrl, + data.attachmentTitle.toString() + ) } } - return true - } - - private fun canWriteToExternalStorage(): Boolean { - return AndroidPermissionsHelper.checkPermission( - itemView.context, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } - - private fun checkWritingPermission() { - val context = itemView.context - if (context is ContextThemeWrapper && context.baseContext is Activity) { - val activity = context.baseContext as Activity - AndroidPermissionsHelper.requestPermission( - activity, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE - ) - } } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt index 8819dd5167..e4215f6f7b 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt @@ -37,7 +37,7 @@ class MessageViewHolder( if (data.isTemporary) Color.GRAY else Color.BLACK ) data.message.let { - text_edit_indicator.isVisible = it.isSystemMessage() && it.editedBy != null + text_edit_indicator.isVisible = !it.isSystemMessage() && it.editedBy != null image_star_indicator.isVisible = it.starred?.isNotEmpty() ?: false } } diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt index 3ec306b1b9..d140d8d040 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt @@ -3,27 +3,32 @@ package chat.rocket.android.chatroom.presentation import chat.rocket.android.R import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatroom.ui.chatRoomIntent -import chat.rocket.android.members.ui.newInstance import chat.rocket.android.server.ui.changeServerIntent import chat.rocket.android.util.extensions.addFragmentBackStack class ChatRoomNavigator(internal val activity: ChatRoomActivity) { - fun toMembersList(chatRoomId: String, chatRoomType: String) { + fun toMembersList(chatRoomId: String) { activity.addFragmentBackStack("MembersFragment", R.id.fragment_container) { - newInstance(chatRoomId, chatRoomType) + chat.rocket.android.members.ui.newInstance(chatRoomId) } } - fun toPinnedMessageList(chatRoomId: String, chatRoomType: String) { + fun toPinnedMessageList(chatRoomId: String) { activity.addFragmentBackStack("PinnedMessages", R.id.fragment_container) { - chat.rocket.android.pinnedmessages.ui.newInstance(chatRoomId, chatRoomType) + chat.rocket.android.pinnedmessages.ui.newInstance(chatRoomId) } } - fun toFavoriteMessageList(chatRoomId: String, chatRoomType: String) { + fun toFavoriteMessageList(chatRoomId: String) { activity.addFragmentBackStack("FavoriteMessages", R.id.fragment_container) { - chat.rocket.android.favoritemessages.ui.newInstance(chatRoomId, chatRoomType) + chat.rocket.android.favoritemessages.ui.newInstance(chatRoomId) + } + } + + fun toFileList(chatRoomId: String) { + activity.addFragmentBackStack("Files", R.id.fragment_container) { + chat.rocket.android.files.ui.newInstance(chatRoomId) } } diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index 923ecfce5c..52483d5861 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -181,9 +181,7 @@ class ChatRoomPresenter @Inject constructor( } subscribeTypingStatus() - if (offset == 0L) { - subscribeState() - } + subscribeState() } } @@ -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) @@ -638,14 +635,17 @@ class ChatRoomPresenter @Inject constructor( } } - fun toMembersList(chatRoomId: String, chatRoomType: String) = - navigator.toMembersList(chatRoomId, chatRoomType) + fun toMembersList(chatRoomId: String) = + navigator.toMembersList(chatRoomId) + + fun toPinnedMessageList(chatRoomId: String) = + navigator.toPinnedMessageList(chatRoomId) - fun toPinnedMessageList(chatRoomId: String, chatRoomType: String) = - navigator.toPinnedMessageList(chatRoomId, chatRoomType) + fun toFavoriteMessageList(chatRoomId: String) = + navigator.toFavoriteMessageList(chatRoomId) - fun toFavoriteMessageList(chatRoomId: String, chatRoomType: String) = - navigator.toFavoriteMessageList(chatRoomId, chatRoomType) + fun toFileList(chatRoomId: String) = + navigator.toFileList(chatRoomId) fun loadChatRooms() { launchUI(strategy) { @@ -787,12 +787,14 @@ class ChatRoomPresenter @Inject constructor( } private suspend fun subscribeTypingStatus() { - client.subscribeTypingStatus(chatRoomId.toString()) { _, id -> - typingStatusSubscriptionId = id - } + launch(CommonPool + strategy.jobs) { + client.subscribeTypingStatus(chatRoomId.toString()) { _, id -> + typingStatusSubscriptionId = id + } - for (typingStatus in client.typingStatusChannel) { - processTypingStatus(typingStatus) + for (typingStatus in client.typingStatusChannel) { + processTypingStatus(typingStatus) + } } } @@ -834,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) { diff --git a/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt b/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt index 948302c5b7..ed43d7c82d 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt @@ -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) diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt index 4762cea676..ce72a45ea6 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt @@ -1,10 +1,13 @@ package chat.rocket.android.chatroom.ui +import DrawableHelper import android.content.Context import android.content.Intent import android.os.Bundle import android.support.v4.app.Fragment import android.support.v7.app.AppCompatActivity +import android.text.SpannableStringBuilder +import androidx.core.view.isVisible import chat.rocket.android.R import chat.rocket.android.chatroom.presentation.ChatRoomNavigator import chat.rocket.android.server.domain.GetCurrentServerInteractor @@ -63,14 +66,6 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { @Inject lateinit var managerFactory: ConnectionManagerFactory - private lateinit var chatRoomId: String - private lateinit var chatRoomName: String - private lateinit var chatRoomType: String - private var isChatRoomReadOnly: Boolean = false - private var isChatRoomSubscribed: Boolean = true - private var isChatRoomCreator: Boolean = false - private var chatRoomLastSeen: Long = -1L - override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) @@ -85,33 +80,33 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { return } - chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) + val chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" } - chatRoomName = intent.getStringExtra(INTENT_CHAT_ROOM_NAME) + val chatRoomName = intent.getStringExtra(INTENT_CHAT_ROOM_NAME) requireNotNull(chatRoomName) { "no chat_room_name provided in Intent extras" } - chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE) + val chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE) requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" } - isChatRoomReadOnly = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, true) - requireNotNull(isChatRoomReadOnly) { "no chat_room_is_read_only provided in Intent extras" } + val isChatRoomReadOnly = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, true) - isChatRoomCreator = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_CREATOR, false) - requireNotNull(isChatRoomCreator) { "no chat_room_is_creator provided in Intent extras" } + val isChatRoomCreator = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_CREATOR, false) - val chatRoomMessage = intent.getStringExtra(INTENT_CHAT_ROOM_MESSAGE) + val chatRoomLastSeen = intent.getLongExtra(INTENT_CHAT_ROOM_LAST_SEEN, -1) - setupToolbar() + val isChatRoomSubscribed = intent.getBooleanExtra(INTENT_CHAT_IS_SUBSCRIBED, true) - chatRoomLastSeen = intent.getLongExtra(INTENT_CHAT_ROOM_LAST_SEEN, -1) + val chatRoomMessage = intent.getStringExtra(INTENT_CHAT_ROOM_MESSAGE) - isChatRoomSubscribed = intent.getBooleanExtra(INTENT_CHAT_IS_SUBSCRIBED, true) + setupToolbar() if (supportFragmentManager.findFragmentByTag(TAG_CHAT_ROOM_FRAGMENT) == null) { addFragment(TAG_CHAT_ROOM_FRAGMENT, R.id.fragment_container) { - newInstance(chatRoomId, chatRoomName, chatRoomType, isChatRoomReadOnly, chatRoomLastSeen, - isChatRoomSubscribed, isChatRoomCreator, chatRoomMessage) + newInstance( + chatRoomId, chatRoomName, chatRoomType, isChatRoomReadOnly, chatRoomLastSeen, + isChatRoomSubscribed, isChatRoomCreator, chatRoomMessage + ) } } } @@ -128,43 +123,34 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { setSupportActionBar(toolbar) supportActionBar?.setDisplayShowTitleEnabled(false) toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) - text_room_name.textContent = chatRoomName - - showRoomTypeIcon(true) - toolbar.setNavigationOnClickListener { finishActivity() } } - 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 - } + fun showToolbarTitle(title: String) { + text_room_name.textContent = title + } - drawable?.let { - val wrappedDrawable = DrawableHelper.wrapDrawable(it) - val mutableDrawable = wrappedDrawable.mutate() - DrawableHelper.tintDrawable(mutableDrawable, this, R.color.white) - DrawableHelper.compoundDrawable(text_room_name, mutableDrawable) + fun showToolbarChatRoomIcon(chatRoomType: String) { + val drawable = when (roomTypeOf(chatRoomType)) { + is RoomType.Channel -> { + DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_black_12dp, this) } - } else { - text_room_name.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + is RoomType.PrivateGroup -> { + DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_12_dp, this) + } + else -> null } - } + drawable?.let { + val wrappedDrawable = DrawableHelper.wrapDrawable(it) + val mutableDrawable = wrappedDrawable.mutate() + DrawableHelper.tintDrawable(mutableDrawable, this, R.color.colorWhite) + DrawableHelper.compoundDrawable(text_room_name, mutableDrawable) + } + } - fun setupToolbarTitle(toolbarTitle: String) { - text_room_name.textContent = toolbarTitle + fun hideToolbarChatRoomIcon() { + text_room_name.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) } private fun finishActivity() { diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt index d0cefeed5f..2d8bba2d56 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt @@ -13,22 +13,12 @@ import android.support.v4.app.Fragment import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView -import android.view.KeyEvent -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.text.SpannableStringBuilder +import android.view.* import androidx.core.text.bold import androidx.core.view.isVisible import chat.rocket.android.R -import chat.rocket.android.chatroom.adapter.ChatRoomAdapter -import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter -import chat.rocket.android.chatroom.adapter.PEOPLE -import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter -import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter +import chat.rocket.android.chatroom.adapter.* import chat.rocket.android.chatroom.presentation.ChatRoomPresenter import chat.rocket.android.chatroom.presentation.ChatRoomView import chat.rocket.android.chatroom.viewmodel.BaseViewModel @@ -39,26 +29,10 @@ import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewMod import chat.rocket.android.helper.EndlessRecyclerViewScrollListener import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.MessageParser -import chat.rocket.android.util.extensions.asObservable -import chat.rocket.android.util.extensions.circularRevealOrUnreveal -import chat.rocket.android.util.extensions.fadeIn -import chat.rocket.android.util.extensions.fadeOut -import chat.rocket.android.util.extensions.hideKeyboard -import chat.rocket.android.util.extensions.inflate -import chat.rocket.android.util.extensions.isAtBottom -import chat.rocket.android.util.extensions.rotateBy -import chat.rocket.android.util.extensions.setVisible -import chat.rocket.android.util.extensions.showToast -import chat.rocket.android.util.extensions.textContent -import chat.rocket.android.util.extensions.ui -import chat.rocket.android.widget.emoji.ComposerEditText -import chat.rocket.android.widget.emoji.Emoji -import chat.rocket.android.widget.emoji.EmojiKeyboardListener -import chat.rocket.android.widget.emoji.EmojiKeyboardPopup -import chat.rocket.android.widget.emoji.EmojiListenerAdapter -import chat.rocket.android.widget.emoji.EmojiParser -import chat.rocket.android.widget.emoji.EmojiPickerPopup -import chat.rocket.android.widget.emoji.EmojiReactionListener +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 @@ -187,8 +161,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR setupFab() setupSuggestionsView() setupActionSnackbar() - activity?.apply { - (this as? ChatRoomActivity)?.showRoomTypeIcon(true) + (activity as ChatRoomActivity).let { + it.showToolbarTitle(chatRoomName) + it.showToolbarChatRoomIcon(chatRoomType) } } @@ -230,13 +205,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_members_list -> { - presenter.toMembersList(chatRoomId, chatRoomType) + presenter.toMembersList(chatRoomId) } R.id.action_pinned_messages -> { - presenter.toPinnedMessageList(chatRoomId, chatRoomType) + presenter.toPinnedMessageList(chatRoomId) } R.id.action_favorite_messages -> { - presenter.toFavoriteMessageList(chatRoomId, chatRoomType) + presenter.toFavoriteMessageList(chatRoomId) + } + R.id.action_files -> { + presenter.toFileList(chatRoomId) } } return true @@ -267,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 @@ -289,12 +269,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } - override fun onRoomUpdated(userCanPost: Boolean, channelIsBroadcast: Boolean, userCanMod: Boolean) { + override fun onRoomUpdated( + userCanPost: Boolean, + channelIsBroadcast: Boolean, + userCanMod: Boolean + ) { // 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. - setupMessageComposer(userCanPost) - isBroadcastChannel = channelIsBroadcast - if (isBroadcastChannel && !userCanMod) activity?.invalidateOptionsMenu() + ui { + setupMessageComposer(userCanPost) + isBroadcastChannel = channelIsBroadcast + if (isBroadcastChannel && !userCanMod) activity?.invalidateOptionsMenu() + } } override fun openDirectMessage(chatRoom: ChatRoom, permalink: String) { @@ -613,15 +599,15 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR handler.postDelayed(dismissStatus, 2000) } is State.Disconnected -> connection_status_text.text = - getString(R.string.status_disconnected) + getString(R.string.status_disconnected) is State.Connecting -> connection_status_text.text = - getString(R.string.status_connecting) + getString(R.string.status_connecting) is State.Authenticating -> connection_status_text.text = - getString(R.string.status_authenticating) + getString(R.string.status_authenticating) is State.Disconnecting -> connection_status_text.text = - getString(R.string.status_disconnecting) + getString(R.string.status_disconnecting) is State.Waiting -> connection_status_text.text = - getString(R.string.status_waiting, state.seconds) + getString(R.string.status_waiting, state.seconds) } } } @@ -666,19 +652,18 @@ 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) subscribeComposeTextMessage() emojiKeyboardPopup = - EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container)) + EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container)) emojiKeyboardPopup.listener = this text_message.listener = object : ComposerEditText.ComposerEditTextListener { override fun onKeyboardOpened() { @@ -807,14 +792,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 } } @@ -844,6 +829,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } private fun setupToolbar(toolbarTitle: String) { - (activity as ChatRoomActivity).setupToolbarTitle(toolbarTitle) + (activity as ChatRoomActivity).showToolbarTitle(toolbarTitle) } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/bottomsheet/adapter/ActionListAdapter.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/bottomsheet/adapter/ActionListAdapter.kt index c6ab4a1751..db0cb73b17 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/bottomsheet/adapter/ActionListAdapter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/bottomsheet/adapter/ActionListAdapter.kt @@ -26,7 +26,7 @@ class ActionListAdapter( holder.itemView.setOnClickListener { callback?.onMenuItemClick(item) } - val deleteTextColor = holder.itemView.context.resources.getColor(R.color.red) + val deleteTextColor = holder.itemView.context.resources.getColor(R.color.colorRed) val color = if (item.itemId == R.id.action_message_delete) { deleteTextColor } else { diff --git a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageViewModel.kt b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageViewModel.kt index 030294e59e..e5df5ff1ac 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageViewModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageViewModel.kt @@ -4,19 +4,19 @@ import chat.rocket.android.R import chat.rocket.core.model.Message data class MessageViewModel( - override val message: Message, - override val rawData: Message, - override val messageId: String, - override val avatar: String, - override val time: CharSequence, - override val senderName: CharSequence, - override val content: CharSequence, - override val isPinned: Boolean, - override var reactions: List, - override var nextDownStreamMessage: BaseViewModel<*>? = null, - override var preview: Message? = null, - var isFirstUnread: Boolean, - override var isTemporary: Boolean = false + override val message: Message, + override val rawData: Message, + override val messageId: String, + override val avatar: String, + override val time: CharSequence, + override val senderName: CharSequence, + override val content: CharSequence, + override val isPinned: Boolean, + override var reactions: List, + override var nextDownStreamMessage: BaseViewModel<*>? = null, + override var preview: Message? = null, + var isFirstUnread: Boolean, + override var isTemporary: Boolean = false ) : BaseMessageViewModel { override val viewType: Int get() = BaseViewModel.ViewType.MESSAGE.viewType diff --git a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ReactionViewModel.kt b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ReactionViewModel.kt index 6a12b9f792..79b0540a08 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ReactionViewModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ReactionViewModel.kt @@ -1,9 +1,9 @@ package chat.rocket.android.chatroom.viewmodel data class ReactionViewModel( - val messageId: String, - val shortname: String, - val unicode: CharSequence, - val count: Int, - val usernames: List = emptyList() + val messageId: String, + val shortname: String, + val unicode: CharSequence, + val count: Int, + val usernames: List = emptyList() ) \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt index 3586cf3948..d0fa1b8b97 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt @@ -65,70 +65,131 @@ 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> { + suspend fun map( + message: Message, + roomViewModel: RoomViewModel = RoomViewModel(roles = emptyList(), isBroadcast = true) + ): List> { return translate(message, roomViewModel) } - suspend fun map(messages: List, roomViewModel: RoomViewModel = RoomViewModel( - roles = emptyList(), isBroadcast = true)): List> = withContext(CommonPool) { - val list = ArrayList>(messages.size) - - messages.forEach { - list.addAll(translate(it, roomViewModel)) + suspend fun map( + messages: List, + roomViewModel: RoomViewModel = RoomViewModel(roles = emptyList(), isBroadcast = true), + asNotReversed: Boolean = false + ): List> = + withContext(CommonPool) { + val list = ArrayList>(messages.size) + + messages.forEach { + list.addAll( + if (asNotReversed) translateAsNotReversed(it, roomViewModel) + else translate(it, roomViewModel) + ) + } + return@withContext list } - return@withContext list - } + private suspend fun translate( + message: Message, + roomViewModel: RoomViewModel + ): List> = + withContext(CommonPool) { + val list = ArrayList>() - private suspend fun translate(message: Message, roomViewModel: RoomViewModel) - : List> = withContext(CommonPool) { - val list = ArrayList>() + message.urls?.forEach { + val url = mapUrl(message, it) + url?.let { list.add(url) } + } - message.urls?.forEach { - val url = mapUrl(message, it) - url?.let { list.add(url) } - } + message.attachments?.forEach { + val attachment = mapAttachment(message, it) + attachment?.let { list.add(attachment) } + } - message.attachments?.forEach { - val attachment = mapAttachment(message, it) - attachment?.let { list.add(attachment) } - } + mapMessage(message).let { + if (list.isNotEmpty()) { + it.preview = list.first().preview + } + list.add(it) + } - mapMessage(message).let { - if (list.isNotEmpty()) { - it.preview = list.first().preview + 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.add(it) - } - for (i in list.size - 1 downTo 0) { - val next = if (i - 1 < 0) null else list[i - 1] - list[i].nextDownStreamMessage = next + return@withContext list } - if (isBroadcastReplyAvailable(roomViewModel, message)) { - roomsInteractor.getById(currentServer, message.roomId)?.let { chatRoom -> - val replyViewModel = mapMessageReply(message, chatRoom) - list.first().nextDownStreamMessage = replyViewModel - list.add(0, replyViewModel) + private suspend fun translateAsNotReversed( + message: Message, + roomViewModel: RoomViewModel + ): List> = + withContext(CommonPool) { + val list = ArrayList>() + + 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) + } } - } - return@withContext list - } + 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 && - !message.isSystemMessage() && - senderUsername != currentUsername + !message.isSystemMessage() && + senderUsername != currentUsername } 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, diff --git a/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt b/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt index c30ea599c4..cb258748e1 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt @@ -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 diff --git a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt index c29a5998c2..e57ec76b4d 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt @@ -92,11 +92,11 @@ class ChatRoomsAdapter( private fun bindIcon(chatRoom: ChatRoom, imageView: ImageView) { val drawable = when (chatRoom.type) { is RoomType.Channel -> DrawableHelper.getDrawableFromId( - R.drawable.ic_hashtag_12dp, + R.drawable.ic_hashtag_black_12dp, context ) is RoomType.PrivateGroup -> DrawableHelper.getDrawableFromId( - R.drawable.ic_lock_12_dp, + R.drawable.ic_lock_black_12_dp, context ) is RoomType.DirectMessage -> DrawableHelper.getUserStatusDrawable( diff --git a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt index d5cfe94c81..fe5eefbf55 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt @@ -1,8 +1,6 @@ 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() { diff --git a/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt b/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt index 50e329aabe..8d63f39c73 100644 --- a/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt +++ b/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt @@ -11,13 +11,14 @@ import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.chatroom.di.ChatRoomFragmentProvider import chat.rocket.android.chatroom.di.ChatRoomModule import chat.rocket.android.chatroom.di.FavoriteMessagesFragmentProvider -import chat.rocket.android.chatroom.di.PinnedMessagesFragmentProvider import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider import chat.rocket.android.dagger.scope.PerActivity +import chat.rocket.android.files.di.FilesFragmentProvider import chat.rocket.android.main.di.MainModule import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.members.di.MembersFragmentProvider +import chat.rocket.android.pinnedmessages.di.PinnedMessagesFragmentProvider import chat.rocket.android.profile.di.ProfileFragmentProvider import chat.rocket.android.server.di.ChangeServerModule import chat.rocket.android.server.ui.ChangeServerActivity @@ -30,21 +31,25 @@ import dagger.android.ContributesAndroidInjector abstract class ActivityBuilder { @PerActivity - @ContributesAndroidInjector(modules = [AuthenticationModule::class, - ServerFragmentProvider::class, - LoginFragmentProvider::class, - RegisterUsernameFragmentProvider::class, - ResetPasswordFragmentProvider::class, - SignupFragmentProvider::class, - TwoFAFragmentProvider::class - ]) + @ContributesAndroidInjector( + modules = [AuthenticationModule::class, + ServerFragmentProvider::class, + LoginFragmentProvider::class, + RegisterUsernameFragmentProvider::class, + ResetPasswordFragmentProvider::class, + SignupFragmentProvider::class, + TwoFAFragmentProvider::class + ] + ) abstract fun bindAuthenticationActivity(): AuthenticationActivity @PerActivity - @ContributesAndroidInjector(modules = [MainModule::class, - ChatRoomsFragmentProvider::class, - ProfileFragmentProvider::class - ]) + @ContributesAndroidInjector( + modules = [MainModule::class, + ChatRoomsFragmentProvider::class, + ProfileFragmentProvider::class + ] + ) abstract fun bindMainActivity(): MainActivity @PerActivity @@ -54,7 +59,8 @@ abstract class ActivityBuilder { ChatRoomFragmentProvider::class, MembersFragmentProvider::class, PinnedMessagesFragmentProvider::class, - FavoriteMessagesFragmentProvider::class + FavoriteMessagesFragmentProvider::class, + FilesFragmentProvider::class ] ) abstract fun bindChatRoomActivity(): ChatRoomActivity diff --git a/app/src/main/java/chat/rocket/android/dagger/module/ServiceBuilder.kt b/app/src/main/java/chat/rocket/android/dagger/module/ServiceBuilder.kt index d6334c40ec..bf60c25021 100644 --- a/app/src/main/java/chat/rocket/android/dagger/module/ServiceBuilder.kt +++ b/app/src/main/java/chat/rocket/android/dagger/module/ServiceBuilder.kt @@ -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 diff --git a/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt b/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt index 82aa1e774b..c18ab22d0f 100644 --- a/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt +++ b/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt @@ -15,37 +15,36 @@ import javax.inject.Inject class FavoriteMessagesPresenter @Inject constructor( private val view: FavoriteMessagesView, private val strategy: CancelStrategy, - private val serverInteractor: GetCurrentServerInteractor, private val roomsInteractor: ChatRoomsInteractor, private val mapper: ViewModelMapper, - factory: RocketChatClientFactory + val serverInteractor: GetCurrentServerInteractor, + val factory: RocketChatClientFactory ) { - private val client = factory.create(serverInteractor.get()!!) + private val serverUrl = serverInteractor.get()!! + private val client = factory.create(serverUrl) 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) { try { - val serverUrl = serverInteractor.get()!! - val chatRoom = roomsInteractor.getById(serverUrl, roomId) - chatRoom?.let { room -> - view.showLoading() - val favoriteMessages = - client.getFavoriteMessages(roomId, room.type, offset) - offset = favoriteMessages.offset.toInt() - val messageList = mapper.map(favoriteMessages.result) + view.showLoading() + roomsInteractor.getById(serverUrl, roomId)?.let { + val favoriteMessages = client.getFavoriteMessages(roomId, it.type, offset) + val messageList = mapper.map(favoriteMessages.result, asNotReversed = true) view.showFavoriteMessages(messageList) - view.hideLoading() + offset += 1 * 30 }.ifNull { Timber.e("Couldn't find a room with id: $roomId at current server.") } - } catch (e: RocketChatException) { - Timber.e(e) + } catch (exception: RocketChatException) { + Timber.e(exception) + } finally { + view.hideLoading() } } } diff --git a/app/src/main/java/chat/rocket/android/favoritemessages/ui/FavoriteMessagesFragment.kt b/app/src/main/java/chat/rocket/android/favoritemessages/ui/FavoriteMessagesFragment.kt index 123b22169e..08a760d55a 100644 --- a/app/src/main/java/chat/rocket/android/favoritemessages/ui/FavoriteMessagesFragment.kt +++ b/app/src/main/java/chat/rocket/android/favoritemessages/ui/FavoriteMessagesFragment.kt @@ -23,21 +23,18 @@ import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.fragment_favorite_messages.* import javax.inject.Inject -fun newInstance(chatRoomId: String, chatRoomType: String): Fragment { +fun newInstance(chatRoomId: String): Fragment { return FavoriteMessagesFragment().apply { arguments = Bundle(1).apply { putString(INTENT_CHAT_ROOM_ID, chatRoomId) - putString(INTENT_CHAT_ROOM_TYPE, chatRoomType) } } } private const val INTENT_CHAT_ROOM_ID = "chat_room_id" -private const val INTENT_CHAT_ROOM_TYPE = "chat_room_type" class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { private lateinit var chatRoomId: String - private lateinit var chatRoomType: String private lateinit var adapter: ChatRoomAdapter @Inject lateinit var presenter: FavoriteMessagesPresenter @@ -49,7 +46,6 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { val bundle = arguments if (bundle != null) { chatRoomId = bundle.getString(INTENT_CHAT_ROOM_ID) - chatRoomType = bundle.getString(INTENT_CHAT_ROOM_TYPE) } else { requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } } @@ -70,13 +66,13 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { override fun showFavoriteMessages(favoriteMessages: List>) { ui { if (recycler_view.adapter == null) { - adapter = ChatRoomAdapter(chatRoomType, "", null, false) + adapter = ChatRoomAdapter(enableActions = false) recycler_view.adapter = adapter val linearLayoutManager = 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( @@ -114,6 +110,9 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { } private fun setupToolbar() { - (activity as ChatRoomActivity).setupToolbarTitle(getString(R.string.title_favorite_messages)) + (activity as ChatRoomActivity).let { + it.showToolbarTitle(getString(R.string.title_favorite_messages)) + it.hideToolbarChatRoomIcon() + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/adapter/FilesAdapter.kt b/app/src/main/java/chat/rocket/android/files/adapter/FilesAdapter.kt new file mode 100644 index 0000000000..43ce95e0c8 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/adapter/FilesAdapter.kt @@ -0,0 +1,71 @@ +package chat.rocket.android.files.adapter + +import android.support.v7.widget.RecyclerView +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import chat.rocket.android.R +import chat.rocket.android.files.viewmodel.FileViewModel +import chat.rocket.android.util.extensions.inflate +import kotlinx.android.synthetic.main.item_generic_attachment.view.* + +class FilesAdapter(private val listener: (FileViewModel) -> Unit) : + RecyclerView.Adapter() { + private var dataSet: List = ArrayList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilesAdapter.ViewHolder = + ViewHolder(parent.inflate(R.layout.item_generic_attachment)) + + override fun onBindViewHolder(holder: FilesAdapter.ViewHolder, position: Int) = + holder.bind(dataSet[position], listener) + + override fun getItemCount(): Int = dataSet.size + + fun prependData(dataSet: List) { + this.dataSet = dataSet + notifyItemRangeInserted(0, dataSet.size) + } + + fun appendData(dataSet: List) { + val previousDataSetSize = this.dataSet.size + this.dataSet += dataSet + notifyItemRangeInserted(previousDataSetSize, dataSet.size) + } + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun bind(fileViewModel: FileViewModel, listener: (FileViewModel) -> Unit) { + with(itemView) { + when { + fileViewModel.isImage -> { + image_file_thumbnail.setImageURI(fileViewModel.url) + image_file_media_thumbnail.isVisible = false + image_file_thumbnail.isVisible = true + } + fileViewModel.isMedia -> { + image_file_media_thumbnail.setImageDrawable( + context.resources.getDrawable( + R.drawable.ic_play_arrow_black_24dp, null + ) + ) + image_file_thumbnail.isVisible = false + image_file_media_thumbnail.isVisible = true + } + else -> { + image_file_media_thumbnail.setImageDrawable( + context.resources.getDrawable( + R.drawable.ic_insert_drive_file_black_24dp, null + ) + ) + image_file_thumbnail.isVisible = false + image_file_media_thumbnail.isVisible = true + } + } + text_file_name.text = fileViewModel.name + text_uploader.text = fileViewModel.uploader + text_upload_date.text = fileViewModel.uploadDate + setOnClickListener { listener(fileViewModel) } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/di/FilesFragmentModule.kt b/app/src/main/java/chat/rocket/android/files/di/FilesFragmentModule.kt new file mode 100644 index 0000000000..0b31c76a80 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/di/FilesFragmentModule.kt @@ -0,0 +1,30 @@ +package chat.rocket.android.files.di + +import android.arch.lifecycle.LifecycleOwner +import chat.rocket.android.core.lifecycle.CancelStrategy +import chat.rocket.android.dagger.scope.PerFragment +import chat.rocket.android.files.presentation.FilesView +import chat.rocket.android.files.ui.FilesFragment +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.experimental.Job + +@Module +@PerFragment +class FilesFragmentModule { + + @Provides + fun provideLifecycleOwner(frag: FilesFragment): LifecycleOwner { + return frag + } + + @Provides + fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { + return CancelStrategy(owner, jobs) + } + + @Provides + fun provideFilesView(frag: FilesFragment): FilesView { + return frag + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/di/FilesFragmentProvider.kt b/app/src/main/java/chat/rocket/android/files/di/FilesFragmentProvider.kt new file mode 100644 index 0000000000..95075b0282 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/di/FilesFragmentProvider.kt @@ -0,0 +1,12 @@ +package chat.rocket.android.files.di + +import chat.rocket.android.files.ui.FilesFragment +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class FilesFragmentProvider { + + @ContributesAndroidInjector(modules = [FilesFragmentModule::class]) + abstract fun provideFilesFragment(): FilesFragment +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/presentation/FilesPresenter.kt b/app/src/main/java/chat/rocket/android/files/presentation/FilesPresenter.kt new file mode 100644 index 0000000000..c338b9f649 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/presentation/FilesPresenter.kt @@ -0,0 +1,72 @@ +package chat.rocket.android.files.presentation + +import androidx.core.net.toUri +import chat.rocket.android.core.lifecycle.CancelStrategy +import chat.rocket.android.files.viewmodel.FileViewModel +import chat.rocket.android.files.viewmodel.FileViewModelMapper +import chat.rocket.android.server.domain.ChatRoomsInteractor +import chat.rocket.android.server.domain.GetCurrentServerInteractor +import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.util.extensions.launchUI +import chat.rocket.common.RocketChatException +import chat.rocket.common.util.ifNull +import chat.rocket.core.internal.rest.getFiles +import timber.log.Timber +import javax.inject.Inject + +class FilesPresenter @Inject constructor( + private val view: FilesView, + private val strategy: CancelStrategy, + private val roomsInteractor: ChatRoomsInteractor, + private val mapper: FileViewModelMapper, + val serverInteractor: GetCurrentServerInteractor, + val factory: RocketChatClientFactory +) { + private val serverUrl = serverInteractor.get()!! + private val client = factory.create(serverUrl) + private var offset: Int = 0 + + /** + * Load all files for the given room id. + * + * @param roomId The id of the room to get files from. + */ + fun loadFiles(roomId: String) { + launchUI(strategy) { + try { + view.showLoading() + roomsInteractor.getById(serverUrl, roomId)?.let { + val files = client.getFiles(roomId, it.type, offset) + val filesViewModel = mapper.mapToViewModelList(files.result) + view.showFiles(filesViewModel, files.total) + offset += 1 * 30 + }.ifNull { + Timber.e("Couldn't find a room with id: $roomId at current server.") + } + } catch (exception: RocketChatException) { + exception.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + Timber.e(exception) + } finally { + view.hideLoading() + } + } + } + + fun openFile(fileViewModel: FileViewModel) { + when { + fileViewModel.isImage -> fileViewModel.url?.let { + view.openImage(it, fileViewModel.name ?: "") + } + fileViewModel.isMedia -> fileViewModel.url?.let { + view.playMedia(it) + } + else -> fileViewModel.url?.let { + view.openDocument(it.toUri()) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/presentation/FilesView.kt b/app/src/main/java/chat/rocket/android/files/presentation/FilesView.kt new file mode 100644 index 0000000000..5a4594aa2b --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/presentation/FilesView.kt @@ -0,0 +1,39 @@ +package chat.rocket.android.files.presentation + +import android.net.Uri +import chat.rocket.android.core.behaviours.LoadingView +import chat.rocket.android.core.behaviours.MessageView +import chat.rocket.android.files.viewmodel.FileViewModel + +interface FilesView : MessageView, LoadingView { + + /** + * Show list of files for the current room. + * + * @param dataSet The data set to show. + * @param total The total number of files. + */ + fun showFiles(dataSet: List, total: Long) + + /** + * Plays a media file (audio/video). + * + * @param url The file url to play its media. + */ + fun playMedia(url: String) + + /** + * Opens an image file + * + * @param url The file url to open its image. + * @param name The file name. + */ + fun openImage(url: String, name: String) + + /** + * Opens a document file (.pdf, .txt and so on). + * + * @param uri The file uri to open its document. + */ + fun openDocument(uri: Uri) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/ui/FilesFragment.kt b/app/src/main/java/chat/rocket/android/files/ui/FilesFragment.kt new file mode 100644 index 0000000000..2653640fe8 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/ui/FilesFragment.kt @@ -0,0 +1,151 @@ +package chat.rocket.android.files.ui + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import chat.rocket.android.R +import chat.rocket.android.chatroom.ui.ChatRoomActivity +import chat.rocket.android.files.adapter.FilesAdapter +import chat.rocket.android.files.presentation.FilesPresenter +import chat.rocket.android.files.presentation.FilesView +import chat.rocket.android.files.viewmodel.FileViewModel +import chat.rocket.android.helper.EndlessRecyclerViewScrollListener +import chat.rocket.android.helper.ImageHelper +import chat.rocket.android.player.PlayerActivity +import chat.rocket.android.util.extensions.inflate +import chat.rocket.android.util.extensions.showToast +import chat.rocket.android.util.extensions.ui +import chat.rocket.android.widget.DividerItemDecoration +import dagger.android.support.AndroidSupportInjection +import kotlinx.android.synthetic.main.fragment_files.* +import javax.inject.Inject + +fun newInstance(chatRoomId: String): Fragment { + return FilesFragment().apply { + arguments = Bundle(1).apply { + putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) + } + } +} + +private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id" + +class FilesFragment : Fragment(), FilesView { + @Inject + lateinit var presenter: FilesPresenter + private val adapter: FilesAdapter = + FilesAdapter { fileViewModel -> presenter.openFile(fileViewModel) } + private val linearLayoutManager = + LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + private lateinit var chatRoomId: String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + AndroidSupportInjection.inject(this) + + val bundle = arguments + if (bundle != null) { + chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) + } else { + requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = container?.inflate(R.layout.fragment_files) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecyclerView() + presenter.loadFiles(chatRoomId) + } + + override fun showFiles(dataSet: List, total: Long) { + setupToolbar(total) + if (adapter.itemCount == 0) { + adapter.prependData(dataSet) + if (dataSet.size >= 30) { + recycler_view.addOnScrollListener(object : + EndlessRecyclerViewScrollListener(linearLayoutManager) { + override fun onLoadMore( + page: Int, + totalItemsCount: Int, + recyclerView: RecyclerView? + ) { + presenter.loadFiles(chatRoomId) + } + }) + } + } else { + adapter.appendData(dataSet) + + } + } + + override fun playMedia(url: String) { + ui { + PlayerActivity.play(it, url) + } + } + + override fun openImage(url: String, name: String) { + ui { + ImageHelper.openImage(root_layout.context, url, name) + } + } + + override fun openDocument(uri: Uri) { + ui { + startActivity(Intent(Intent.ACTION_VIEW, uri)) + } + } + + override fun showMessage(resId: Int) { + ui { + showToast(resId) + } + } + + override fun showMessage(message: String) { + ui { + showToast(message) + } + } + + override fun showGenericErrorMessage() { + showMessage(getString(R.string.msg_generic_error)) + } + + override fun showLoading() { + ui { view_loading.isVisible = true } + } + + override fun hideLoading() { + ui { view_loading.isVisible = false } + } + + private fun setupRecyclerView() { + ui { + recycler_view.layoutManager = linearLayoutManager + recycler_view.addItemDecoration(DividerItemDecoration(it)) + recycler_view.adapter = adapter + } + } + + private fun setupToolbar(totalFiles: Long) { + (activity as ChatRoomActivity).let { + it.showToolbarTitle(getString(R.string.title_files_total, totalFiles)) + it.hideToolbarChatRoomIcon() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/viewmodel/FileViewModel.kt b/app/src/main/java/chat/rocket/android/files/viewmodel/FileViewModel.kt new file mode 100644 index 0000000000..7ae3507788 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/viewmodel/FileViewModel.kt @@ -0,0 +1,73 @@ +package chat.rocket.android.files.viewmodel + +import DateTimeHelper +import chat.rocket.android.server.domain.TokenRepository +import chat.rocket.android.server.domain.useRealName +import chat.rocket.android.util.extensions.fileUrl +import chat.rocket.core.model.Value +import chat.rocket.core.model.attachment.GenericAttachment + +class FileViewModel( + private val genericAttachment: GenericAttachment, + private val settings: Map>, + private val tokenRepository: TokenRepository, + private val baseUrl: String +) { + val name: String? + val uploader: String? + val uploadDate: String? + val url: String? + val isMedia: Boolean + val isImage: Boolean + + init { + name = getFileName() + uploader = getUserDisplayName() + uploadDate = getFileUploadDate() + url = getFileUrl() + isMedia = isFileMedia() + isImage = isFileImage() + } + + private fun getFileName(): String? { + return genericAttachment.name + } + + private fun getUserDisplayName(): String { + val username = "@${genericAttachment.user?.username}" + val realName = genericAttachment.user?.name + val uploaderName = if (settings.useRealName()) realName else username + return uploaderName ?: username + } + + private fun getFileUploadDate(): String { + genericAttachment.uploadedAt?.let { + return DateTimeHelper.getDateTime(DateTimeHelper.getLocalDateTime(it)) + } + return "" + } + + private fun getFileUrl(): String? { + val token = tokenRepository.get(baseUrl) + if (token != null) { + genericAttachment.path?.let { + return baseUrl.fileUrl(it, token) + } + } + return "" + } + + private fun isFileMedia(): Boolean { + genericAttachment.type?.let { + return it.contains("audio") || it.contains("video") + } + return false + } + + private fun isFileImage(): Boolean { + genericAttachment.type?.let { + return it.contains("image") + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/files/viewmodel/FileViewModelMapper.kt b/app/src/main/java/chat/rocket/android/files/viewmodel/FileViewModelMapper.kt new file mode 100644 index 0000000000..711caaf0c9 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/files/viewmodel/FileViewModelMapper.kt @@ -0,0 +1,23 @@ +package chat.rocket.android.files.viewmodel + +import chat.rocket.android.server.domain.GetCurrentServerInteractor +import chat.rocket.android.server.domain.GetSettingsInteractor +import chat.rocket.android.server.domain.TokenRepository +import chat.rocket.android.server.domain.baseUrl +import chat.rocket.core.model.Value +import chat.rocket.core.model.attachment.GenericAttachment +import javax.inject.Inject + +class FileViewModelMapper @Inject constructor( + serverInteractor: GetCurrentServerInteractor, + getSettingsInteractor: GetSettingsInteractor, + private val tokenRepository: TokenRepository +) { + private var settings: Map> = + getSettingsInteractor.get(serverInteractor.get()!!) + private val baseUrl = settings.baseUrl() + + fun mapToViewModelList(fileList: List): List { + return fileList.map { FileViewModel(it, settings, tokenRepository, baseUrl) } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/helper/ImageHelper.kt b/app/src/main/java/chat/rocket/android/helper/ImageHelper.kt new file mode 100644 index 0000000000..fae2123e5d --- /dev/null +++ b/app/src/main/java/chat/rocket/android/helper/ImageHelper.kt @@ -0,0 +1,167 @@ +package chat.rocket.android.helper + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.media.MediaScannerConnection +import android.os.Environment +import android.support.design.widget.AppBarLayout +import android.support.v7.widget.Toolbar +import android.text.TextUtils +import android.util.TypedValue +import android.view.ContextThemeWrapper +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.core.net.toUri +import androidx.core.view.setPadding +import chat.rocket.android.R +import com.facebook.binaryresource.FileBinaryResource +import com.facebook.cache.common.CacheKey +import com.facebook.imageformat.ImageFormatChecker +import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory +import com.facebook.imagepipeline.core.ImagePipelineFactory +import com.facebook.imagepipeline.request.ImageRequest +import com.facebook.imagepipeline.request.ImageRequestBuilder +import com.stfalcon.frescoimageviewer.ImageViewer +import timber.log.Timber +import java.io.File + +object ImageHelper { + private var cacheKey: CacheKey? = null + + // TODO - implement a proper image viewer with a proper Transition + // TODO - We should definitely write our own ImageViewer + fun openImage(context: Context, imageUrl: String, imageName: String) { + var imageViewer: ImageViewer? = null + val request = + ImageRequestBuilder.newBuilderWithSource(imageUrl.toUri()) + .setLowestPermittedRequestLevel(ImageRequest.RequestLevel.DISK_CACHE) + .build() + + cacheKey = DefaultCacheKeyFactory.getInstance() + .getEncodedCacheKey(request, null) + val pad = context.resources + .getDimensionPixelSize(R.dimen.viewer_toolbar_padding) + + val lparams = AppBarLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + val toolbar = Toolbar(context).also { + it.inflateMenu(R.menu.image_actions) + it.setOnMenuItemClickListener { + return@setOnMenuItemClickListener when (it.itemId) { + R.id.action_save_image -> saveImage(context) + else -> true + } + } + + val titleSize = context.resources + .getDimensionPixelSize(R.dimen.viewer_toolbar_title) + val titleTextView = TextView(context).also { + it.text = imageName + it.setTextColor(Color.WHITE) + it.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize.toFloat()) + it.ellipsize = TextUtils.TruncateAt.END + it.setSingleLine() + it.typeface = Typeface.DEFAULT_BOLD + it.setPadding(pad) + } + + val backArrowView = ImageView(context).also { + it.setImageResource(R.drawable.ic_arrow_back_white_24dp) + it.setOnClickListener { imageViewer?.onDismiss() } + it.setPadding(0, pad, pad, pad) + } + + val layoutParams = AppBarLayout.LayoutParams( + AppBarLayout.LayoutParams.WRAP_CONTENT, + AppBarLayout.LayoutParams.WRAP_CONTENT + ) + + it.addView(backArrowView, layoutParams) + it.addView(titleTextView, layoutParams) + } + + val appBarLayout = AppBarLayout(context).also { + it.layoutParams = lparams + it.setBackgroundColor(Color.BLACK) + it.addView( + toolbar, AppBarLayout.LayoutParams( + AppBarLayout.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ) + } + + val builder = ImageViewer.createPipelineDraweeControllerBuilder() + .setImageRequest(request) + .setAutoPlayAnimations(true) + + imageViewer = ImageViewer.Builder(context, listOf(imageUrl)) + .setOverlayView(appBarLayout) + .setStartPosition(0) + .hideStatusBar(false) + .setCustomDraweeControllerBuilder(builder) + .show() + } + + private fun saveImage(context: Context): Boolean { + if (!canWriteToExternalStorage(context)) { + checkWritingPermission(context) + return false + } + if (ImagePipelineFactory.getInstance().mainFileCache.hasKey(cacheKey)) { + val resource = ImagePipelineFactory.getInstance().mainFileCache.getResource(cacheKey) + val cachedFile = (resource as FileBinaryResource).file + val imageFormat = ImageFormatChecker.getImageFormat(resource.openStream()) + val imageDir = "${Environment.DIRECTORY_PICTURES}/Rocket.Chat Images/" + val imagePath = Environment.getExternalStoragePublicDirectory(imageDir) + val imageFile = + File(imagePath, "${cachedFile.nameWithoutExtension}.${imageFormat.fileExtension}") + imagePath.mkdirs() + imageFile.createNewFile() + try { + cachedFile.copyTo(imageFile, true) + MediaScannerConnection.scanFile( + context, + arrayOf(imageFile.absolutePath), + null + ) { path, uri -> + Timber.i("Scanned $path:") + Timber.i("-> uri=$uri") + } + } catch (ex: Exception) { + Timber.e(ex) + val message = context.getString(R.string.msg_image_saved_failed) + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } finally { + val message = context.getString(R.string.msg_image_saved_successfully) + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + } + return true + } + + private fun canWriteToExternalStorage(context: Context): Boolean { + return AndroidPermissionsHelper.checkPermission( + context, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + } + + private fun checkWritingPermission(context: Context) { + if (context is ContextThemeWrapper && context.baseContext is Activity) { + val activity = context.baseContext as Activity + AndroidPermissionsHelper.requestPermission( + activity, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/helper/MessageParser.kt b/app/src/main/java/chat/rocket/android/helper/MessageParser.kt index f041eb48ef..679d1568f9 100644 --- a/app/src/main/java/chat/rocket/android/helper/MessageParser.kt +++ b/app/src/main/java/chat/rocket/android/helper/MessageParser.kt @@ -93,7 +93,7 @@ class MessageParser @Inject constructor( private val othersTextColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme) private val othersBackgroundColor = ResourcesCompat.getColor(context.resources, android.R.color.transparent, context.theme) - private val myselfTextColor = ResourcesCompat.getColor(context.resources, R.color.white, context.theme) + private val myselfTextColor = ResourcesCompat.getColor(context.resources, R.color.colorWhite, context.theme) private val myselfBackgroundColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme) private val mentionPadding = context.resources.getDimensionPixelSize(R.dimen.padding_mention).toFloat() private val mentionRadius = context.resources.getDimensionPixelSize(R.dimen.radius_mention).toFloat() diff --git a/app/src/main/java/chat/rocket/android/helper/SmartLockHelper.kt b/app/src/main/java/chat/rocket/android/helper/SmartLockHelper.kt new file mode 100644 index 0000000000..6c09f5deff --- /dev/null +++ b/app/src/main/java/chat/rocket/android/helper/SmartLockHelper.kt @@ -0,0 +1,146 @@ +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 diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt index 675dd8fb7b..b8fe187854 100644 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt +++ b/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt @@ -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() } diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt index 32f446c2ed..8c41bbe9e9 100644 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt +++ b/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt @@ -49,7 +49,7 @@ class MainPresenter @Inject constructor( private val userDataChannel = Channel() - fun toChatList() = navigator.toChatList() + fun toChatList(chatRoomId: String? = null) = navigator.toChatList(chatRoomId) fun toUserProfile() = navigator.toUserProfile() @@ -105,11 +105,12 @@ 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() } } @@ -177,6 +178,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") } diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainView.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainView.kt index 7c1a69f9af..0aea24fca9 100644 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainView.kt +++ b/app/src/main/java/chat/rocket/android/main/presentation/MainView.kt @@ -24,4 +24,10 @@ interface MainView : MessageView, VersionCheckView { fun setupNavHeader(viewModel: NavHeaderViewModel, accounts: List) fun closeServerSelection() + fun invalidateToken(token: String) + + /** + * callback to disable auto sign in for google smart lock when the user logs out + */ + fun disableAutoSignIn() } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt b/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt index 0b0a156d32..2b72311cdb 100644 --- a/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt +++ b/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt @@ -1,5 +1,6 @@ package chat.rocket.android.main.ui +import DrawableHelper import android.app.Activity import android.app.AlertDialog import android.os.Bundle @@ -11,19 +12,22 @@ import android.view.MenuItem import android.view.View import chat.rocket.android.BuildConfig import chat.rocket.android.R -import chat.rocket.android.main.adapter.Selector import chat.rocket.android.main.adapter.AccountsAdapter +import chat.rocket.android.main.adapter.Selector 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.gcm.GoogleCloudMessaging -import com.google.android.gms.iid.InstanceID +import com.google.firebase.iid.FirebaseInstanceId +import com.google.firebase.messaging.FirebaseMessaging +import com.google.android.gms.auth.api.Auth +import com.google.android.gms.common.api.GoogleApiClient import dagger.android.AndroidInjection import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector @@ -37,39 +41,81 @@ import kotlinx.coroutines.experimental.launch import timber.log.Timber import javax.inject.Inject -class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupportFragmentInjector { - @Inject lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector - @Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector - @Inject lateinit var presenter: MainPresenter +class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupportFragmentInjector, + GoogleApiClient.ConnectionCallbacks { + @Inject + lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector + @Inject + lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector + @Inject + 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 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 disableAutoSignIn() { + googleApiClient.let { + if (it.isConnected) { + Auth.CredentialsApi.disableAutoSignIn(googleApiClient) + } + } + } + override fun onResume() { super.onResume() if (!isFragmentAdded) { - presenter.toChatList() + presenter.toChatList(chatRoomId) isFragmentAdded = true } } @@ -119,19 +165,33 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp 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() + .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() + .setMessage( + getString( + R.string.msg_ver_not_minimum, + BuildConfig.REQUIRED_SERVER_VERSION + ) + ) + .setOnDismissListener { presenter.logout() } + .setPositiveButton(R.string.msg_ok, null) + .create() + .show() + } + + override fun invalidateToken(token: String) { + FirebaseInstanceId.getInstance().deleteToken(token, FirebaseMessaging.INSTANCE_ID_SCOPE) } private fun setupAccountsList(header: View, accounts: List) { @@ -176,7 +236,8 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, HasSupp override fun activityInjector(): AndroidInjector = activityDispatchingAndroidInjector - override fun supportFragmentInjector(): AndroidInjector = fragmentDispatchingAndroidInjector + override fun supportFragmentInjector(): AndroidInjector = + fragmentDispatchingAndroidInjector private fun setupToolbar() { setSupportActionBar(toolbar) diff --git a/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt b/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt index f2019a4d27..f6144b99d9 100644 --- a/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt +++ b/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt @@ -3,39 +3,44 @@ package chat.rocket.android.members.presentation import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.members.viewmodel.MemberViewModel import chat.rocket.android.members.viewmodel.MemberViewModelMapper +import chat.rocket.android.server.domain.ChatRoomsInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.util.extensions.launchUI -import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException -import chat.rocket.common.model.roomTypeOf import chat.rocket.common.util.ifNull import chat.rocket.core.RocketChatClient import chat.rocket.core.internal.rest.getMembers +import timber.log.Timber import javax.inject.Inject class MembersPresenter @Inject constructor( private val view: MembersView, private val navigator: MembersNavigator, private val strategy: CancelStrategy, - serverInteractor: GetCurrentServerInteractor, - factory: RocketChatClientFactory, - private val mapper: MemberViewModelMapper + private val roomsInteractor: ChatRoomsInteractor, + private val mapper: MemberViewModelMapper, + val serverInteractor: GetCurrentServerInteractor, + val factory: RocketChatClientFactory ) { - private val client: RocketChatClient = factory.create(serverInteractor.get()!!) + private val serverUrl = serverInteractor.get()!! + private val client: RocketChatClient = factory.create(serverUrl) + private var offset: Long = 0 - fun loadChatRoomsMembers(chatRoomId: String, chatRoomType: String, offset: Long = 0) { + fun loadChatRoomsMembers(roomId: String) { launchUI(strategy) { try { view.showLoading() - - val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") { - client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 60) + roomsInteractor.getById(serverUrl, roomId)?.let { + val members = client.getMembers(it.id, it.type, offset, 60) + val memberViewModels = mapper.mapToViewModelList(members.result) + view.showMembers(memberViewModels, members.total) + offset += 1 * 60L + }.ifNull { + Timber.e("Couldn't find a room with id: $roomId at current server") } - val memberViewModels = mapper.mapToViewModelList(members.result) - view.showMembers(memberViewModels, members.total) - } catch (ex: RocketChatException) { - ex.message?.let { + } catch (exception: RocketChatException) { + exception.message?.let { view.showMessage(it) }.ifNull { view.showGenericErrorMessage() diff --git a/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt b/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt index 37fc3c088a..168bb21ad1 100644 --- a/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt +++ b/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt @@ -2,7 +2,6 @@ package chat.rocket.android.members.ui import android.os.Bundle import android.support.v4.app.Fragment -import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater @@ -24,17 +23,15 @@ import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.fragment_members.* import javax.inject.Inject -fun newInstance(chatRoomId: String, chatRoomType: String): Fragment { +fun newInstance(chatRoomId: String): Fragment { return MembersFragment().apply { arguments = Bundle(1).apply { putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) - putString(BUNDLE_CHAT_ROOM_TYPE, chatRoomType) } } } private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id" -private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type" class MembersFragment : Fragment(), MembersView { @Inject @@ -43,9 +40,7 @@ class MembersFragment : Fragment(), MembersView { MembersAdapter { memberViewModel -> presenter.toMemberDetails(memberViewModel) } private val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) - private lateinit var chatRoomId: String - private lateinit var chatRoomType: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -54,7 +49,6 @@ class MembersFragment : Fragment(), MembersView { val bundle = arguments if (bundle != null) { chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) - chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE) } else { requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } } @@ -68,9 +62,8 @@ class MembersFragment : Fragment(), MembersView { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupRecyclerView() - presenter.loadChatRoomsMembers(chatRoomId, chatRoomType) + presenter.loadChatRoomsMembers(chatRoomId) } override fun showMembers(dataSet: List, total: Long) { @@ -86,7 +79,7 @@ class MembersFragment : Fragment(), MembersView { totalItemsCount: Int, recyclerView: RecyclerView? ) { - presenter.loadChatRoomsMembers(chatRoomId, chatRoomType, page * 60L) + presenter.loadChatRoomsMembers(chatRoomId) } }) } @@ -127,8 +120,9 @@ class MembersFragment : Fragment(), MembersView { } private fun setupToolbar(totalMembers: Long) { - (activity as ChatRoomActivity?)?.setupToolbarTitle( - getString(R.string.title_members, totalMembers) - ) + (activity as ChatRoomActivity).let { + it.showToolbarTitle(getString(R.string.title_members, totalMembers)) + it.hideToolbarChatRoomIcon() + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/pinnedmessages/di/PinnedMessagesFragmentProvider.kt b/app/src/main/java/chat/rocket/android/pinnedmessages/di/PinnedMessagesFragmentProvider.kt index 372118671c..8bce8e9d21 100644 --- a/app/src/main/java/chat/rocket/android/pinnedmessages/di/PinnedMessagesFragmentProvider.kt +++ b/app/src/main/java/chat/rocket/android/pinnedmessages/di/PinnedMessagesFragmentProvider.kt @@ -1,5 +1,6 @@ -package chat.rocket.android.chatroom.di +package chat.rocket.android.pinnedmessages.di +import chat.rocket.android.chatroom.di.PinnedMessagesFragmentModule import chat.rocket.android.pinnedmessages.ui.PinnedMessagesFragment import dagger.Module import dagger.android.ContributesAndroidInjector diff --git a/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt b/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt index 3fb2a0b337..10699f9cbf 100644 --- a/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt +++ b/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt @@ -16,37 +16,36 @@ import javax.inject.Inject class PinnedMessagesPresenter @Inject constructor( private val view: PinnedMessagesView, private val strategy: CancelStrategy, - private val serverInteractor: GetCurrentServerInteractor, private val roomsInteractor: ChatRoomsInteractor, private val mapper: ViewModelMapper, - factory: RocketChatClientFactory + val serverInteractor: GetCurrentServerInteractor, + val factory: RocketChatClientFactory ) { - private val client = factory.create(serverInteractor.get()!!) - private var pinnedMessagesListOffset: Int = 0 + private val serverUrl = serverInteractor.get()!! + private val client = factory.create(serverUrl) + 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. */ fun loadPinnedMessages(roomId: String) { launchUI(strategy) { try { - val serverUrl = serverInteractor.get()!! - val chatRoom = roomsInteractor.getById(serverUrl, roomId) - chatRoom?.let { room -> - view.showLoading() - val pinnedMessages = - client.getPinnedMessages(roomId, room.type, pinnedMessagesListOffset) - pinnedMessagesListOffset = pinnedMessages.offset.toInt() - val messageList = mapper.map(pinnedMessages.result.filterNot { it.isSystemMessage() }) + view.showLoading() + roomsInteractor.getById(serverUrl, roomId)?.let { + val pinnedMessages = client.getPinnedMessages(roomId, it.type, offset) + val messageList = mapper.map(pinnedMessages.result, asNotReversed = true) view.showPinnedMessages(messageList) - view.hideLoading() + offset += 1 * 30 }.ifNull { Timber.e("Couldn't find a room with id: $roomId at current server.") } - } catch (e: RocketChatException) { - Timber.e(e) + } catch (exception: RocketChatException) { + Timber.e(exception) + } finally { + view.hideLoading() } } } diff --git a/app/src/main/java/chat/rocket/android/pinnedmessages/ui/PinnedMessagesFragment.kt b/app/src/main/java/chat/rocket/android/pinnedmessages/ui/PinnedMessagesFragment.kt index a979413099..acdc68a76b 100644 --- a/app/src/main/java/chat/rocket/android/pinnedmessages/ui/PinnedMessagesFragment.kt +++ b/app/src/main/java/chat/rocket/android/pinnedmessages/ui/PinnedMessagesFragment.kt @@ -23,22 +23,19 @@ import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.fragment_pinned_messages.* import javax.inject.Inject -fun newInstance(chatRoomId: String, chatRoomType: String): Fragment { +fun newInstance(chatRoomId: String): Fragment { return PinnedMessagesFragment().apply { arguments = Bundle(1).apply { putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) - putString(BUNDLE_CHAT_ROOM_TYPE, chatRoomType) } } } private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id" -private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type" class PinnedMessagesFragment : Fragment(), PinnedMessagesView { private lateinit var chatRoomId: String - private lateinit var chatRoomType: String private lateinit var adapter: ChatRoomAdapter @Inject lateinit var presenter: PinnedMessagesPresenter @@ -50,7 +47,6 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { val bundle = arguments if (bundle != null) { chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) - chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE) } else { requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } } @@ -66,20 +62,19 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { super.onViewCreated(view, savedInstanceState) setupToolbar() - presenter.loadPinnedMessages(chatRoomId) } override fun showPinnedMessages(pinnedMessages: List>) { ui { if (recycler_view_pinned.adapter == null) { - adapter = ChatRoomAdapter(chatRoomType, "", null, false) + adapter = ChatRoomAdapter(enableActions = false) recycler_view_pinned.adapter = adapter val linearLayoutManager = 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( @@ -121,6 +116,9 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { } private fun setupToolbar() { - (activity as ChatRoomActivity).setupToolbarTitle(getString(R.string.title_pinned_messages)) + (activity as ChatRoomActivity).let { + it.showToolbarTitle(getString(R.string.title_pinned_messages)) + it.hideToolbarChatRoomIcon() + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt b/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt index 8017b6f68b..2fd2e779d8 100644 --- a/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt +++ b/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt @@ -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) { diff --git a/app/src/main/java/chat/rocket/android/push/FirebaseMessagingService.kt b/app/src/main/java/chat/rocket/android/push/FirebaseMessagingService.kt new file mode 100644 index 0000000000..ef4b7cb942 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/push/FirebaseMessagingService.kt @@ -0,0 +1,24 @@ +package chat.rocket.android.push + +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 FirebaseMessagingService : FirebaseMessagingService() { + + @Inject + lateinit var pushManager: PushManager + + override fun onCreate() { + super.onCreate() + AndroidInjection.inject(this) + } + + 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 diff --git a/app/src/main/java/chat/rocket/android/push/FirebaseTokenService.kt b/app/src/main/java/chat/rocket/android/push/FirebaseTokenService.kt index 247bd0ef4a..3c57cc15f6 100644 --- a/app/src/main/java/chat/rocket/android/push/FirebaseTokenService.kt +++ b/app/src/main/java/chat/rocket/android/push/FirebaseTokenService.kt @@ -1,14 +1,12 @@ 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 diff --git a/app/src/main/java/chat/rocket/android/push/GcmListenerService.kt b/app/src/main/java/chat/rocket/android/push/GcmListenerService.kt deleted file mode 100644 index ea910521fc..0000000000 --- a/app/src/main/java/chat/rocket/android/push/GcmListenerService.kt +++ /dev/null @@ -1,23 +0,0 @@ -package chat.rocket.android.push - -import android.os.Bundle -import com.google.android.gms.gcm.GcmListenerService -import dagger.android.AndroidInjection -import javax.inject.Inject - -class GcmListenerService : GcmListenerService() { - - @Inject - lateinit var pushManager: PushManager - - override fun onCreate() { - super.onCreate() - AndroidInjection.inject(this) - } - - override fun onMessageReceived(from: String?, data: Bundle?) { - data?.let { - pushManager.handle(data) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/push/PushManager.kt b/app/src/main/java/chat/rocket/android/push/PushManager.kt index 827dd812ef..0003cd481f 100644 --- a/app/src/main/java/chat/rocket/android/push/PushManager.kt +++ b/app/src/main/java/chat/rocket/android/push/PushManager.kt @@ -41,14 +41,15 @@ import javax.inject.Inject * for old source code. */ class PushManager @Inject constructor( - private val groupedPushes: GroupedPush, - private val manager: NotificationManager, - private val moshi: Moshi, - private val getAccountInteractor: GetAccountInteractor, - private val getSettingsInteractor: GetSettingsInteractor, - private val context: Context + private val groupedPushes: GroupedPush, + private val manager: NotificationManager, + private val moshi: Moshi, + private val getAccountInteractor: GetAccountInteractor, + 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::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 { @@ -137,7 +146,7 @@ class PushManager @Inject constructor( val host = info.host val builder = createBaseNotificationBuilder(pushMessage, grouped = true) - .setGroupSummary(true) + .setGroupSummary(true) if (style == null || style == "inbox") { val pushMessageList = groupedPushes.hostToPushMessageList[host] @@ -150,7 +159,7 @@ class PushManager @Inject constructor( builder.setContentTitle(getTitle(count, title)) val inbox = NotificationCompat.InboxStyle() - .setBigContentTitle(getTitle(count, title)) + .setBigContentTitle(getTitle(count, title)) for (push in pushMessageList) { inbox.addLine(push.message) @@ -160,8 +169,8 @@ class PushManager @Inject constructor( } } else { val bigText = NotificationCompat.BigTextStyle() - .bigText(message.fromHtml()) - .setBigContentTitle(title.fromHtml()) + .bigText(message.fromHtml()) + .setBigContentTitle(title.fromHtml()) builder.setStyle(bigText) } @@ -177,12 +186,12 @@ class PushManager @Inject constructor( val host = info.host val builder = createBaseNotificationBuilder(pushMessage) - .setGroupSummary(false) + .setGroupSummary(false) if (style == null || "inbox" == style) { val pushMessageList = groupedPushes.hostToPushMessageList.get(host) - pushMessageList?.let { + if (pushMessageList != null) { val userMessages = pushMessageList.filter { it.notificationId == pushMessage.notificationId } @@ -203,18 +212,23 @@ class PushManager @Inject constructor( builder.setStyle(inbox) } else { val bigTextStyle = NotificationCompat.BigTextStyle() - .bigText(message.fromHtml()) + .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() - .bigText(message.fromHtml()) + .bigText(message.fromHtml()) builder.setStyle(bigTextStyle) } - return builder.addReplyAction(pushMessage) - .build() + return builder.addReplyAction(pushMessage).build() } } @@ -227,21 +241,35 @@ class PushManager @Inject constructor( val deleteIntent = getDismissIntent(context, pushMessage) val builder = NotificationCompat.Builder(context, host) - .setWhen(info.createdAt) - .setContentTitle(title.fromHtml()) - .setContentText(message.fromHtml()) - .setGroup(host) - .setDeleteIntent(deleteIntent) - .setContentIntent(contentIntent) - .setMessageNotification() + .setWhen(info.createdAt) + .setContentTitle(title.fromHtml()) + .setContentText(message.fromHtml()) + .setGroup(host) + .setDeleteIntent(deleteIntent) + .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 @@ -265,18 +293,18 @@ class PushManager @Inject constructor( private fun getDismissIntent(context: Context, pushMessage: PushMessage): PendingIntent { val deleteIntent = Intent(context, DeleteReceiver::class.java) - .putExtra(EXTRA_NOT_ID, pushMessage.notificationId.toInt()) - .putExtra(EXTRA_HOSTNAME, pushMessage.info.host) + .putExtra(EXTRA_NOT_ID, pushMessage.notificationId.toInt()) + .putExtra(EXTRA_HOSTNAME, pushMessage.info.host) return PendingIntent.getBroadcast(context, pushMessage.notificationId.toInt(), deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT) } 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 @@ -288,13 +316,13 @@ class PushManager @Inject constructor( private fun NotificationCompat.Builder.addReplyAction(pushMessage: PushMessage): NotificationCompat.Builder { val replyTextHint = context.getText(R.string.notif_action_reply_hint) val replyRemoteInput = RemoteInput.Builder(REMOTE_INPUT_REPLY) - .setLabel(replyTextHint) - .build() + .setLabel(replyTextHint) + .build() val pendingIntent = getReplyPendingIntent(pushMessage) val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_action_message_reply_24dp, replyTextHint, pendingIntent) - .addRemoteInput(replyRemoteInput) - .setAllowGeneratedReplies(true) - .build() + .addRemoteInput(replyRemoteInput) + .setAllowGeneratedReplies(true) + .build() this.addAction(replyAction) return this @@ -317,17 +345,17 @@ class PushManager @Inject constructor( val replyIntent = getReplyIntent(pushMessage) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { PendingIntent.getBroadcast( - context, - randomizer.nextInt(), - replyIntent, - PendingIntent.FLAG_UPDATE_CURRENT + context, + random.nextInt(), + replyIntent, + PendingIntent.FLAG_UPDATE_CURRENT ) } else { PendingIntent.getActivity( - context, - randomizer.nextInt(), - replyIntent, - PendingIntent.FLAG_UPDATE_CURRENT + context, + random.nextInt(), + replyIntent, + PendingIntent.FLAG_UPDATE_CURRENT ) } } @@ -336,7 +364,7 @@ class PushManager @Inject constructor( val alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) val res = context.resources val smallIcon = res.getIdentifier( - "rocket_chat_notification", "drawable", context.packageName) + "rocket_chat_notification", "drawable", context.packageName) with(this, { setAutoCancel(true) setShowWhen(true) @@ -350,24 +378,25 @@ class PushManager @Inject constructor( } data class PushMessage( - val title: String, - val message: String, - val info: PushInfo, - val image: String? = null, - val count: String? = null, - val notificationId: String, - val summaryText: String? = null, - val style: String? = null + val title: String, + val message: String, + val info: PushInfo, + val image: String? = null, + val count: String? = null, + val notificationId: String, + val summaryText: String? = null, + val style: String? = null ) : Parcelable { + constructor(parcel: Parcel) : this( - parcel.readString(), - parcel.readString(), - parcel.readParcelable(PushMessage::class.java.classLoader), - parcel.readString(), - parcel.readString(), - parcel.readString(), - parcel.readString(), - parcel.readString()) + parcel.readString(), + parcel.readString(), + parcel.readParcelable(PushMessage::class.java.classLoader), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString()) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(title) @@ -397,11 +426,11 @@ data class PushMessage( @JsonSerializable data class PushInfo @KotshiConstructor constructor( - @Json(name = "host") val hostname: String, - @Json(name = "rid") val roomId: String, - val type: RoomType, - val name: String?, - val sender: PushSender? + @Json(name = "host") val hostname: String, + @Json(name = "rid") val roomId: String, + val type: RoomType, + val name: String?, + val sender: PushSender? ) : Parcelable { val createdAt: Long get() = System.currentTimeMillis() @@ -410,11 +439,11 @@ data class PushInfo @KotshiConstructor constructor( } constructor(parcel: Parcel) : this( - parcel.readString(), - parcel.readString(), - roomTypeOf(parcel.readString()), - parcel.readString(), - parcel.readParcelable(PushInfo::class.java.classLoader)) + parcel.readString(), + parcel.readString(), + roomTypeOf(parcel.readString()), + parcel.readString(), + parcel.readParcelable(PushInfo::class.java.classLoader)) private fun sanitizeUrl(baseUrl: String): String { var url = baseUrl.trim() @@ -438,6 +467,9 @@ data class PushInfo @KotshiConstructor constructor( } companion object CREATOR : Parcelable.Creator { + val EMPTY = PushInfo(hostname = "", roomId = "", type = RoomType.CHANNEL, name = "", + sender = null) + override fun createFromParcel(parcel: Parcel): PushInfo { return PushInfo(parcel) } @@ -450,14 +482,14 @@ data class PushInfo @KotshiConstructor constructor( @JsonSerializable data class PushSender @KotshiConstructor constructor( - @Json(name = "_id") val id: String, - val username: String?, - val name: String? + @Json(name = "_id") val id: String, + val username: String?, + val name: String? ) : Parcelable { constructor(parcel: Parcel) : this( - parcel.readString(), - parcel.readString(), - parcel.readString()) + parcel.readString(), + parcel.readString(), + parcel.readString()) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(id) diff --git a/app/src/main/java/chat/rocket/android/push/di/GcmListenerServiceProvider.kt b/app/src/main/java/chat/rocket/android/push/di/FirebaseMessagingServiceProvider.kt similarity index 53% rename from app/src/main/java/chat/rocket/android/push/di/GcmListenerServiceProvider.kt rename to app/src/main/java/chat/rocket/android/push/di/FirebaseMessagingServiceProvider.kt index 52073b343f..aa3d0a0049 100644 --- a/app/src/main/java/chat/rocket/android/push/di/GcmListenerServiceProvider.kt +++ b/app/src/main/java/chat/rocket/android/push/di/FirebaseMessagingServiceProvider.kt @@ -1,11 +1,11 @@ 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 diff --git a/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt b/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt index b973eaa33f..ffd4911547 100644 --- a/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt @@ -103,5 +103,5 @@ fun PublicSettings.uploadMaxFileSize(): Int { 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? \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt b/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt index 3fc95aa4a1..35e6147543 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt +++ b/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt @@ -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 diff --git a/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerNavigator.kt b/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerNavigator.kt index c489da5dd4..dad5bd2998 100644 --- a/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerNavigator.kt +++ b/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerNavigator.kt @@ -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() } diff --git a/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt b/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt index b321378f1c..57bf7d0c8c 100644 --- a/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt +++ b/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt @@ -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() diff --git a/app/src/main/java/chat/rocket/android/server/ui/ChangeServerActivity.kt b/app/src/main/java/chat/rocket/android/server/ui/ChangeServerActivity.kt index 6029affcb4..e2e3cec7ae 100644 --- a/app/src/main/java/chat/rocket/android/server/ui/ChangeServerActivity.kt +++ b/app/src/main/java/chat/rocket/android/server/ui/ChangeServerActivity.kt @@ -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 } diff --git a/app/src/main/java/chat/rocket/android/util/extensions/String.kt b/app/src/main/java/chat/rocket/android/util/extensions/String.kt index 82e7f43909..5750846b7c 100644 --- a/app/src/main/java/chat/rocket/android/util/extensions/String.kt +++ b/app/src/main/java/chat/rocket/android/util/extensions/String.kt @@ -2,6 +2,7 @@ package chat.rocket.android.util.extensions import android.graphics.Color import android.util.Patterns +import chat.rocket.common.model.Token import timber.log.Timber fun String.removeTrailingSlash(): String { @@ -17,7 +18,11 @@ fun String.sanitize(): String { return tmp.removeTrailingSlash() } -fun String.avatarUrl(avatar: String, isGroupOrChannel: Boolean = false, format: String = "jpeg"): String { +fun String.avatarUrl( + avatar: String, + isGroupOrChannel: Boolean = false, + format: String = "jpeg" +): String { return if (isGroupOrChannel) { "${removeTrailingSlash()}/avatar/%23${avatar.removeTrailingSlash()}?format=$format" } else { @@ -25,6 +30,14 @@ fun String.avatarUrl(avatar: String, isGroupOrChannel: Boolean = false, format: } } +fun String.fileUrl(path: String, token: Token): String { + return (this + path + "?rc_uid=${token.userId}" + "&rc_token=${token.authToken}").safeUrl() +} + +fun String.safeUrl(): String { + return this.replace(" ", "%20").replace("\\", "") +} + fun String.serverLogoUrl(favicon: String) = "${removeTrailingSlash()}/$favicon" fun String.casUrl(serverUrl: String, token: String) = diff --git a/app/src/main/java/chat/rocket/android/webview/oauth/ui/OauthWebViewActivity.kt b/app/src/main/java/chat/rocket/android/webview/oauth/ui/OauthWebViewActivity.kt index 1533f82476..0a7a66a321 100644 --- a/app/src/main/java/chat/rocket/android/webview/oauth/ui/OauthWebViewActivity.kt +++ b/app/src/main/java/chat/rocket/android/webview/oauth/ui/OauthWebViewActivity.kt @@ -40,7 +40,6 @@ class OauthWebViewActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_web_view) - webPageUrl = intent.getStringExtra(INTENT_WEB_PAGE_URL) requireNotNull(webPageUrl) { "no web_page_url provided in Intent extras" } @@ -81,7 +80,8 @@ class OauthWebViewActivity : AppCompatActivity() { domStorageEnabled = true // 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 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" + 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() { @@ -114,8 +114,18 @@ class OauthWebViewActivity : AppCompatActivity() { private fun getCredentialSecret(json: JSONObject): String = json.optString(JSON_CREDENTIAL_SECRET) - private fun closeView(activityResult: Int = Activity.RESULT_CANCELED, credentialToken: String? = null, credentialSecret: String? = null) { - setResult(activityResult, Intent().putExtra(INTENT_OAUTH_CREDENTIAL_TOKEN, credentialToken).putExtra(INTENT_OAUTH_CREDENTIAL_SECRET, credentialSecret)) + private fun closeView( + activityResult: Int = Activity.RESULT_CANCELED, + credentialToken: String? = null, + credentialSecret: String? = null + ) { + setResult( + activityResult, + Intent().putExtra(INTENT_OAUTH_CREDENTIAL_TOKEN, credentialToken).putExtra( + INTENT_OAUTH_CREDENTIAL_SECRET, + credentialSecret + ) + ) finish() overridePendingTransition(R.anim.hold, R.anim.slide_down) } diff --git a/app/src/main/res/drawable/ic_file_download_white_24dp.xml b/app/src/main/res/drawable/ic_file_download_white_24dp.xml new file mode 100644 index 0000000000..5e4188b7aa --- /dev/null +++ b/app/src/main/res/drawable/ic_file_download_white_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_hashtag_12dp.xml b/app/src/main/res/drawable/ic_hashtag_12dp.xml deleted file mode 100644 index 9bc13ca446..0000000000 --- a/app/src/main/res/drawable/ic_hashtag_12dp.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_hashtag_black_12dp.xml b/app/src/main/res/drawable/ic_hashtag_black_12dp.xml new file mode 100644 index 0000000000..d7b7455e2f --- /dev/null +++ b/app/src/main/res/drawable/ic_hashtag_black_12dp.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_insert_drive_file_black_24dp.xml b/app/src/main/res/drawable/ic_insert_drive_file_black_24dp.xml new file mode 100644 index 0000000000..5d88006c77 --- /dev/null +++ b/app/src/main/res/drawable/ic_insert_drive_file_black_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_lock_12_dp.xml b/app/src/main/res/drawable/ic_lock_12_dp.xml deleted file mode 100644 index 346fb8ac9f..0000000000 --- a/app/src/main/res/drawable/ic_lock_12_dp.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_lock_black_12_dp.xml b/app/src/main/res/drawable/ic_lock_black_12_dp.xml new file mode 100644 index 0000000000..ec2a0d9f99 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock_black_12_dp.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml b/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml new file mode 100644 index 0000000000..d7b1f402a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_room_channel.xml b/app/src/main/res/drawable/ic_room_channel.xml deleted file mode 100644 index 93c36d9fbe..0000000000 --- a/app/src/main/res/drawable/ic_room_channel.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_room_dm.xml b/app/src/main/res/drawable/ic_room_dm.xml deleted file mode 100644 index 1556b80fe5..0000000000 --- a/app/src/main/res/drawable/ic_room_dm.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_room_lock.xml b/app/src/main/res/drawable/ic_room_lock.xml deleted file mode 100644 index 251ffbd028..0000000000 --- a/app/src/main/res/drawable/ic_room_lock.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9306bf45d8..dfb8138144 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -42,7 +42,7 @@ android:layout_height="match_parent" android:layout_marginTop="@dimen/nav_header_height" android:alpha="0" - android:background="@color/white" + android:background="@color/colorWhite" android:elevation="20dp" android:visibility="gone" /> diff --git a/app/src/main/res/layout/activity_web_view.xml b/app/src/main/res/layout/activity_web_view.xml index cc2d5bb24c..47d208106b 100644 --- a/app/src/main/res/layout/activity_web_view.xml +++ b/app/src/main/res/layout/activity_web_view.xml @@ -21,7 +21,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" - app:indicatorColor="@color/black" + app:indicatorColor="@color/colorBlack" app:indicatorName="BallPulseIndicator" /> \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar_chat_room.xml b/app/src/main/res/layout/app_bar_chat_room.xml index 1cd69b000e..7059110d42 100644 --- a/app/src/main/res/layout/app_bar_chat_room.xml +++ b/app/src/main/res/layout/app_bar_chat_room.xml @@ -16,35 +16,17 @@ app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> - - - - - - - + android:drawablePadding="@dimen/text_view_drawable_padding" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/colorWhite" + android:textSize="18sp" + android:textStyle="bold" + tools:text="general" /> \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar_password.xml b/app/src/main/res/layout/app_bar_password.xml index 2c714a1cf2..b894a14594 100644 --- a/app/src/main/res/layout/app_bar_password.xml +++ b/app/src/main/res/layout/app_bar_password.xml @@ -25,7 +25,7 @@ android:layout_alignParentStart="true" android:ellipsize="end" android:maxLines="1" - android:textColor="@color/white" + android:textColor="@color/colorWhite" android:textSize="18sp" android:textStyle="bold" tools:text="@string/title_password" /> diff --git a/app/src/main/res/layout/emoji_keyboard.xml b/app/src/main/res/layout/emoji_keyboard.xml index 8af855aebb..f4bfe96ea6 100644 --- a/app/src/main/res/layout/emoji_keyboard.xml +++ b/app/src/main/res/layout/emoji_keyboard.xml @@ -4,7 +4,7 @@ android:id="@+id/emoji_keyboard_container" android:layout_width="match_parent" android:layout_height="0dp" - android:background="@color/white"> + android:background="@color/colorWhite"> + android:background="@color/colorWhite" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_authentication_log_in.xml b/app/src/main/res/layout/fragment_authentication_log_in.xml index 5cfb7ac885..6a6f15443d 100644 --- a/app/src/main/res/layout/fragment_authentication_log_in.xml +++ b/app/src/main/res/layout/fragment_authentication_log_in.xml @@ -32,6 +32,18 @@ app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_headline" /> + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_member_bottom_sheet.xml b/app/src/main/res/layout/fragment_member_bottom_sheet.xml index c66b9835f5..50660e2374 100644 --- a/app/src/main/res/layout/fragment_member_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_member_bottom_sheet.xml @@ -30,7 +30,7 @@ style="@style/TextAppearance.AppCompat.Title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/white" + android:textColor="@color/colorWhite" tools:text="Ronald Perkins" /> diff --git a/app/src/main/res/layout/fragment_members.xml b/app/src/main/res/layout/fragment_members.xml index f10f7d2fd3..70d73ff86c 100644 --- a/app/src/main/res/layout/fragment_members.xml +++ b/app/src/main/res/layout/fragment_members.xml @@ -18,7 +18,7 @@ android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_gravity="center" - app:indicatorColor="@color/black" + app:indicatorColor="@color/colorBlack" app:indicatorName="BallPulseIndicator" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_password.xml b/app/src/main/res/layout/fragment_password.xml index e04add6d0f..e237ad5201 100644 --- a/app/src/main/res/layout/fragment_password.xml +++ b/app/src/main/res/layout/fragment_password.xml @@ -48,7 +48,7 @@ android:layout_height="wrap_content" tools:visibility="visible" android:visibility="gone" - app:indicatorColor="@color/black" + app:indicatorColor="@color/colorBlack" app:indicatorName="BallPulseIndicator" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/fragment_pinned_messages.xml b/app/src/main/res/layout/fragment_pinned_messages.xml index 3474a54c83..374b573441 100644 --- a/app/src/main/res/layout/fragment_pinned_messages.xml +++ b/app/src/main/res/layout/fragment_pinned_messages.xml @@ -20,7 +20,7 @@ android:layout_marginBottom="8dp" android:layout_marginTop="8dp" android:visibility="gone" - app:indicatorColor="@color/black" + app:indicatorColor="@color/colorBlack" app:indicatorName="BallPulseIndicator" app:layout_constraintBottom_toBottomOf="@+id/recycler_view_pinned" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index 5e06b26a39..6c6267eaf4 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -69,7 +69,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" - app:indicatorColor="@color/black" + app:indicatorColor="@color/colorBlack" app:indicatorName="BallPulseIndicator" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml index e71086a000..e93141b776 100644 --- a/app/src/main/res/layout/item_chat.xml +++ b/app/src/main/res/layout/item_chat.xml @@ -27,7 +27,7 @@ app:layout_constraintBottom_toBottomOf="@+id/text_chat_name" app:layout_constraintStart_toEndOf="@+id/image_avatar" app:layout_constraintTop_toTopOf="@+id/text_chat_name" - tools:src="@drawable/ic_hashtag_12dp" /> + tools:src="@drawable/ic_hashtag_black_12dp" /> + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml index 70ec535212..b34bed9357 100644 --- a/app/src/main/res/layout/item_message.xml +++ b/app/src/main/res/layout/item_message.xml @@ -13,21 +13,13 @@ android:paddingStart="@dimen/screen_edge_left_and_right_padding" android:paddingTop="@dimen/message_item_top_and_bottom_padding"> - - @@ -38,13 +30,13 @@ android:layout_gravity="center" android:layout_marginEnd="4dp" android:layout_weight="1" - android:background="@color/red" /> + android:background="@color/colorRed" /> + android:textColor="@color/colorRed" /> + android:background="@color/colorRed" /> + + - - - + android:textColor="@color/colorPrimary" + tools:text="Ronald Perkins" + app:layout_constraintStart_toEndOf="@+id/quote_bar" + app:layout_constraintTop_toTopOf="parent" + android:layout_marginStart="8dp" /> - - + diff --git a/app/src/main/res/layout/message_composer.xml b/app/src/main/res/layout/message_composer.xml index c9e4c441f6..18503c8f13 100644 --- a/app/src/main/res/layout/message_composer.xml +++ b/app/src/main/res/layout/message_composer.xml @@ -21,10 +21,10 @@ android:id="@+id/text_room_is_read_only" android:layout_width="match_parent" android:layout_height="45dp" - android:background="@color/white" + android:background="@color/colorWhite" android:gravity="center" android:text="@string/msg_this_room_is_read_only" - android:textColor="@color/black" + android:textColor="@color/colorBlack" android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/divider" /> @@ -32,7 +32,7 @@ android:id="@+id/button_join_chat" android:layout_width="match_parent" android:layout_height="45dp" - android:background="@color/white" + android:background="@color/colorWhite" android:text="@string/action_join_chat" android:textColor="@color/colorAccent" android:visibility="gone" diff --git a/app/src/main/res/layout/message_list.xml b/app/src/main/res/layout/message_list.xml index 7112ac12ee..b7313b0f5e 100644 --- a/app/src/main/res/layout/message_list.xml +++ b/app/src/main/res/layout/message_list.xml @@ -19,7 +19,7 @@ android:theme="@style/Theme.AppCompat" android:tint="@color/gray_material" android:visibility="invisible" - app:backgroundTint="@color/white" + app:backgroundTint="@color/colorWhite" app:fabSize="mini" app:layout_anchor="@id/recycler_view" app:layout_anchorGravity="bottom|end" /> diff --git a/app/src/main/res/layout/message_url_preview.xml b/app/src/main/res/layout/message_url_preview.xml index 57d2781c59..4de6cb20d0 100644 --- a/app/src/main/res/layout/message_url_preview.xml +++ b/app/src/main/res/layout/message_url_preview.xml @@ -13,6 +13,8 @@ android:id="@+id/image_preview" android:layout_width="70dp" android:layout_height="50dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" app:actualImageScaleType="centerCrop" /> diff --git a/app/src/main/res/layout/suggestion_command_item.xml b/app/src/main/res/layout/suggestion_command_item.xml index 27fad815d5..5111201a4c 100644 --- a/app/src/main/res/layout/suggestion_command_item.xml +++ b/app/src/main/res/layout/suggestion_command_item.xml @@ -18,7 +18,7 @@ android:layout_marginStart="8dp" android:ellipsize="end" android:maxLines="1" - android:textColor="@color/black" + android:textColor="@color/colorBlack" android:textSize="14sp" tools:text="/leave" /> diff --git a/app/src/main/res/layout/suggestion_member_item.xml b/app/src/main/res/layout/suggestion_member_item.xml index d56aee019b..ee5b4b92f1 100644 --- a/app/src/main/res/layout/suggestion_member_item.xml +++ b/app/src/main/res/layout/suggestion_member_item.xml @@ -32,7 +32,7 @@ android:layout_height="wrap_content" android:layout_marginStart="5dp" android:maxLines="1" - android:textColor="@color/black" + android:textColor="@color/colorBlack" android:textSize="16sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/image_status" diff --git a/app/src/main/res/layout/suggestion_room_item.xml b/app/src/main/res/layout/suggestion_room_item.xml index 8c69ae389b..1204ecb832 100644 --- a/app/src/main/res/layout/suggestion_room_item.xml +++ b/app/src/main/res/layout/suggestion_room_item.xml @@ -14,7 +14,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="1" - android:textColor="@color/black" + android:textColor="@color/colorBlack" android:textSize="16sp" tools:text="@tools:sample/full_names" /> diff --git a/app/src/main/res/layout/unread_messages_badge.xml b/app/src/main/res/layout/unread_messages_badge.xml index 4c2d04240b..a5c7e2dbce 100644 --- a/app/src/main/res/layout/unread_messages_badge.xml +++ b/app/src/main/res/layout/unread_messages_badge.xml @@ -11,7 +11,7 @@ android:layout_height="18dp" android:background="@drawable/style_total_unread_messages" android:gravity="center" - android:textColor="@color/white" + android:textColor="@color/colorWhite" android:textSize="10sp" android:visibility="gone" tools:text="99+" diff --git a/app/src/main/res/menu/chatroom_actions.xml b/app/src/main/res/menu/chatroom_actions.xml index 900335537c..a0269d9509 100644 --- a/app/src/main/res/menu/chatroom_actions.xml +++ b/app/src/main/res/menu/chatroom_actions.xml @@ -16,4 +16,9 @@ android:id="@+id/action_favorite_messages" android:title="@string/title_favorite_messages" app:showAsAction="never" /> + + \ No newline at end of file diff --git a/app/src/main/res/menu/image_actions.xml b/app/src/main/res/menu/image_actions.xml index 59399eba92..03784c391b 100644 --- a/app/src/main/res/menu/image_actions.xml +++ b/app/src/main/res/menu/image_actions.xml @@ -4,6 +4,7 @@ + android:icon="@drawable/ic_file_download_white_24dp" + android:title="@string/action_save_to_gallery" + app:showAsAction="always" /> \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4f389c0e63..00e56feb31 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -17,7 +17,7 @@ Acerca de - Conectar"' + Conectar Usa este nombre de usuario Toca en este botón para iniciar sesión o crear una cuenta Términos de Servicio @@ -34,6 +34,9 @@ Ausente Ocupado Invisible + // TODO: Add proper translation. + Save to gallery + @@ -134,6 +137,9 @@ Usuario %1$s no silenciado por %2$s %1$s fue establecido %2$s por %3$s %1$s ya no es %2$s por %3$s + // TODO:Add proper translation. + Credentials saved successfully + Respuesta @@ -158,7 +164,7 @@ Starring is not allowed - Lista de miembros + Miembros Mensajes fijados @@ -171,6 +177,13 @@ No favorite messages All the favorite messages\nappear here + + + Files + Files (%d) + No files + All the files appear here + Tamaño del archivo (%1$d bytes) excedió el tamaño máximo de carga de %2$d bytes diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0fb7189f32..584302eec3 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -34,6 +34,8 @@ Loin Occupé Invisible + // TODO: Add proper translation. + Save to gallery @@ -134,6 +136,9 @@ Utilisateur %1$s non muté par %2$s %1$s a été défini %2$s par %3$s %1$s is no longer %2$s par %3$s + // TODO:Add proper translation. + Credentials saved successfully + Répondre @@ -159,7 +164,7 @@ Starring is not allowed - Liste des membres + Membres Messages épinglés @@ -172,6 +177,13 @@ No favorite messages All the favorite messages\nappear here + + + Files + Files (%d) + No files + All the files appear here + Taille du fichier (%1$d bytes) dépassé la taille de téléchargement maximale de %2$d bytes diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index a107cd5fb1..e69d7f6b8c 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -35,6 +35,8 @@ दूर व्यस्त अदृश्य + // TODO: Add proper translation. + Save to gallery @@ -136,6 +138,9 @@ उपयोगकर्ता %1$s %2$s द्वारा अनम्यूट किया गया %1$s %3$s द्वारा %2$s सेट किया गया था %1$s अब %3$s द्वारा %2$s नहीं है + // TODO:Add proper translation. + Credentials saved successfully + जवाब दें @@ -160,7 +165,7 @@ Starring is not allowed - सदस्यों की सूची + सदस्य पिन किए गए संदेश @@ -173,6 +178,13 @@ No favorite messages All the favorite messages\nappear here + + + Files + Files (%d) + No files + All the files appear here + फ़ाइल का आकार %1$d बाइट्स ने %2$d बाइट्स के अधिकतम अपलोड आकार को पार कर लिया है diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0256faa6c4..73566a6509 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -34,6 +34,7 @@ Ausente Ocupado Invisível + Salvar na galeria @@ -126,6 +127,9 @@ Usuário %1$s saiu do modo mudo por %2$s %1$s foi definido %2$s por %3$s %1$s não é mais %2$s por %3$s + // TODO:Add proper translation. + Credentials saved successfully + Responder @@ -148,7 +152,7 @@ Favoritar não permitido - Lista de Membros + Membros Mensagens Pinadas @@ -156,11 +160,16 @@ Todas as mensagens pinadas\naparecerão aqui - Messagens Favoritas Nenhuma messagem favorita Todas as mensagens favoritas\naparecerão aqui + + Arquivos + Arquivos (%d) + Nenhum arquivo + Todos os arquivos aparecerão aqui + Tamanho de arquivo (%1$d bytes) excedeu tamanho máximo de upload (%2$d bytes) diff --git a/app/src/main/res/values/api_keys.xml b/app/src/main/res/values/api_keys.xml deleted file mode 100644 index 99ca89644b..0000000000 --- a/app/src/main/res/values/api_keys.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - 673693445664 - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8fd3487287..a3bac4be4a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,13 +3,13 @@ #FF303030 - #ff212121 + #FF212121 #FF1976D2 #DE000000 - #787878 - #c1c1c1 + #FF787878 + #FFC1C1C1 #2FE1A8 @@ -17,7 +17,16 @@ #FDD236 #d9d9d9 - #FFFFFF + + #FFFFFFFF + #FF000000 + #FFFF0000 + + #FFa0a0a0 + #FF727272 + #FFf1f1f1 + + @color/colorWhite #9FA2A8 @@ -29,13 +38,6 @@ #4D000000 - #FFFFFFFF - #FF000000 - #FFFF0000 - #FFa0a0a0 - #FF727272 - #FFf1f1f1 - #70F1F1F1 #FF767676 @@ -43,7 +45,7 @@ #A0A0A0 - @android:color/white + @color/colorWhite #AFADAF diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index a254033fc0..6c133c4780 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -11,7 +11,7 @@ 10dp 16dp - 4dp + 8dp 6dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2431348828..d8961f3734 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,7 @@ Away Busy Invisible + Save to gallery @@ -127,6 +128,7 @@ User %1$s unmuted by %2$s %1$s was set %2$s by %3$s %1$s is no longer %2$s by %3$s + Credentials saved successfully Reply @@ -149,7 +151,7 @@ Starring is not allowed - Members List + Members Pinned Messages @@ -161,6 +163,12 @@ No favorite messages All the favorite messages\nappear here + + Files + Files (%d) + No files + All the files appear here + File size %1$d bytes exceeded max upload size of %2$d bytes diff --git a/debug.keystore b/debug.keystore new file mode 100644 index 0000000000..30d607ced9 Binary files /dev/null and b/debug.keystore differ diff --git a/dependencies.gradle b/dependencies.gradle index 942e23a34c..9f94f986db 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -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', @@ -24,7 +25,7 @@ ext { timber : '4.7.0', threeTenABP : '1.0.5', rxBinding : '2.0.0', - fresco : '1.8.1', + fresco : '1.9.0', kotshi : '1.0.2', frescoImageViewer : '0.5.1', markwon : '1.0.3', @@ -58,7 +59,8 @@ 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}", room : "android.arch.persistence.room:runtime:${versions.room}", diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fdfb7e318b..21ba6136d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,3 +4,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionSha256Sum=9af7345c199f1731c187c96d3fe3d31f5405192a42046bafa71d846c3d9adacb diff --git a/player/src/main/java/chat/rocket/android/player/PlayerActivity.kt b/player/src/main/java/chat/rocket/android/player/PlayerActivity.kt index 525c17d58f..87ab95545d 100644 --- a/player/src/main/java/chat/rocket/android/player/PlayerActivity.kt +++ b/player/src/main/java/chat/rocket/android/player/PlayerActivity.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import android.support.v7.app.AppCompatActivity -import android.util.Log import android.view.View import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.DefaultRenderersFactory @@ -72,7 +71,6 @@ class PlayerActivity : AppCompatActivity() { } val uri = Uri.parse(videoUrl) val mediaSource = buildMediaSource(uri) - Log.d("PlayerActivity", "Player with: " + videoUrl) player.prepare(mediaSource, true, false) } @@ -94,7 +92,7 @@ class PlayerActivity : AppCompatActivity() { } companion object { - const private val URL_KEY = "URL_KEY" + private const val URL_KEY = "URL_KEY" fun play(context: Context, url: String) { context.startActivity(Intent(context, PlayerActivity::class.java).apply { putExtra(URL_KEY, url)