Unverified Commit d2468b7b authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #1362 from RocketChat/master

[CHORE] Merge master into beta
parents 9da37707 72fb3e02
...@@ -13,8 +13,8 @@ android { ...@@ -13,8 +13,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2024 versionCode 2026
versionName "2.3.0" versionName "2.3.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
} }
......
...@@ -16,13 +16,22 @@ import chat.rocket.android.app.migration.model.RealmBasedServerInfo ...@@ -16,13 +16,22 @@ import chat.rocket.android.app.migration.model.RealmBasedServerInfo
import chat.rocket.android.app.migration.model.RealmPublicSetting import chat.rocket.android.app.migration.model.RealmPublicSetting
import chat.rocket.android.app.migration.model.RealmSession import chat.rocket.android.app.migration.model.RealmSession
import chat.rocket.android.app.migration.model.RealmUser import chat.rocket.android.app.migration.model.RealmUser
import chat.rocket.android.authentication.domain.model.toToken
import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.dagger.qualifier.ForMessages import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.helper.CrashlyticsTree import chat.rocket.android.helper.CrashlyticsTree
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.infrastructure.installCrashlyticsWrapper
import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.widget.emoji.EmojiRepository import chat.rocket.android.widget.emoji.EmojiRepository
...@@ -34,13 +43,16 @@ import com.facebook.drawee.backends.pipeline.DraweeConfig ...@@ -34,13 +43,16 @@ import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.* import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector
import dagger.android.HasBroadcastReceiverInjector
import dagger.android.HasServiceInjector
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import javax.inject.Inject import javax.inject.Inject
...@@ -69,7 +81,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -69,7 +81,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject @Inject
lateinit var getCurrentServerInteractor: GetCurrentServerInteractor lateinit var getCurrentServerInteractor: GetCurrentServerInteractor
@Inject @Inject
lateinit var multiServerRepository: MultiServerTokenRepository lateinit var settingsInteractor: GetSettingsInteractor
@Inject @Inject
lateinit var settingsRepository: SettingsRepository lateinit var settingsRepository: SettingsRepository
@Inject @Inject
...@@ -81,8 +93,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -81,8 +93,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject @Inject
lateinit var prefs: SharedPreferences lateinit var prefs: SharedPreferences
@Inject @Inject
lateinit var getAccountsInteractor: GetAccountsInteractor
@Inject
lateinit var localRepository: LocalRepository lateinit var localRepository: LocalRepository
@Inject @Inject
...@@ -101,8 +111,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -101,8 +111,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
.lifecycle .lifecycle
.addObserver(appLifecycleObserver) .addObserver(appLifecycleObserver)
// TODO - remove this on the future, temporary migration stuff for pre-release versions.
migrateInternalTokens()
context = WeakReference(applicationContext) context = WeakReference(applicationContext)
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
...@@ -127,6 +135,29 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -127,6 +135,29 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error migrating old accounts") Timber.d(ex, "Error migrating old accounts")
} }
// TODO - remove this
checkCurrentServer()
}
private fun checkCurrentServer() {
val currentServer = getCurrentServerInteractor.get() ?: "<unknown>"
if (currentServer == "<unknown>") {
val message = "null currentServer"
Timber.d(IllegalStateException(message), message)
}
val settings = settingsInteractor.get(currentServer)
if (settings.isEmpty()) {
val message = "Empty settings for: $currentServer"
Timber.d(IllegalStateException(message), message)
}
val baseUrl = settings[SITE_URL]
if (baseUrl == null) {
val message = "Server $currentServer SITE_URL"
Timber.d(IllegalStateException(message), message)
}
} }
private fun migrateFromLegacy() { private fun migrateFromLegacy() {
...@@ -233,32 +264,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -233,32 +264,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
} }
} }
private fun migrateInternalTokens() {
if (!prefs.getBoolean(INTERNAL_TOKEN_MIGRATION_NEEDED, true)) {
Timber.d("Tokens already migrated")
return
}
getCurrentServerInteractor.get()?.let { serverUrl ->
multiServerRepository.get(serverUrl)?.let { token ->
tokenRepository.save(serverUrl, Token(token.userId, token.authToken))
}
}
runBlocking {
getAccountsInteractor.get().forEach { account ->
multiServerRepository.get(account.serverUrl)?.let { token ->
tokenRepository.save(account.serverUrl, token.toToken())
}
}
}
prefs.edit { putBoolean(INTERNAL_TOKEN_MIGRATION_NEEDED, false) }
}
private fun setupCrashlytics() { private fun setupCrashlytics() {
val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build() val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()
Fabric.with(this, Crashlytics.Builder().core(core).build()) Fabric.with(this, Crashlytics.Builder().core(core).build())
installCrashlyticsWrapper(this@RocketChatApplication,
getCurrentServerInteractor, settingsInteractor,
accountRepository, localRepository)
} }
private fun setupFresco() { private fun setupFresco() {
...@@ -301,6 +313,4 @@ private fun LocalRepository.hasMigrated() = getBoolean(LocalRepository.MIGRATION ...@@ -301,6 +313,4 @@ private fun LocalRepository.hasMigrated() = getBoolean(LocalRepository.MIGRATION
private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true) private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true)
private fun LocalRepository.setOldMessagesCleanedUp() = save(CLEANUP_OLD_MESSAGES_NEEDED, false) private fun LocalRepository.setOldMessagesCleanedUp() = save(CLEANUP_OLD_MESSAGES_NEEDED, false)
private const val INTERNAL_TOKEN_MIGRATION_NEEDED = "INTERNAL_TOKEN_MIGRATION_NEEDED"
private const val CLEANUP_OLD_MESSAGES_NEEDED = "CLEANUP_OLD_MESSAGES_NEEDED" private const val CLEANUP_OLD_MESSAGES_NEEDED = "CLEANUP_OLD_MESSAGES_NEEDED"
\ No newline at end of file
...@@ -2,6 +2,7 @@ package chat.rocket.android.authentication.di ...@@ -2,6 +2,7 @@ package chat.rocket.android.authentication.di
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.qualifier.ForAuthentication
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
......
...@@ -41,12 +41,13 @@ class LoginPresenter @Inject constructor( ...@@ -41,12 +41,13 @@ class LoginPresenter @Inject constructor(
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
private val settingsInteractor: GetSettingsInteractor, private val settingsInteractor: GetSettingsInteractor,
serverInteractor: GetCurrentServerInteractor, serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServer: SaveCurrentServerInteractor,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val factory: RocketChatClientFactory private val factory: RocketChatClientFactory
) { ) {
// TODO - we should validate the current server when opening the app, and have a nonnull get() // TODO - we should validate the current server when opening the app, and have a nonnull get()
private val currentServer = serverInteractor.get()!! private var currentServer = serverInteractor.get()!!
private lateinit var client: RocketChatClient private lateinit var client: RocketChatClient
private lateinit var settings: PublicSettings private lateinit var settings: PublicSettings
private lateinit var usernameOrEmail: String private lateinit var usernameOrEmail: String
...@@ -103,6 +104,7 @@ class LoginPresenter @Inject constructor( ...@@ -103,6 +104,7 @@ class LoginPresenter @Inject constructor(
} }
private fun setupConnectionInfo(serverUrl: String) { private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(serverUrl) client = factory.create(serverUrl)
settings = settingsInteractor.get(serverUrl) settings = settingsInteractor.get(serverUrl)
} }
...@@ -325,6 +327,7 @@ class LoginPresenter @Inject constructor( ...@@ -325,6 +327,7 @@ class LoginPresenter @Inject constructor(
val username = retryIO("me()") { client.me().username } val username = retryIO("me()") { client.me().username }
if (username != null) { if (username != null) {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
saveCurrentServer.save(currentServer)
saveAccount(username) saveAccount(username)
saveToken(token) saveToken(token)
registerPushToken() registerPushToken()
......
...@@ -27,7 +27,8 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -27,7 +27,8 @@ class RegisterUsernamePresenter @Inject constructor(
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
serverInteractor: GetCurrentServerInteractor, serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServer: SaveCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor settingsInteractor: GetSettingsInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
...@@ -47,6 +48,7 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -47,6 +48,7 @@ class RegisterUsernamePresenter @Inject constructor(
val registeredUsername = me.username val registeredUsername = me.username
if (registeredUsername != null) { if (registeredUsername != null) {
saveAccount(registeredUsername) saveAccount(registeredUsername)
saveCurrentServer.save(currentServer)
tokenRepository.save(currentServer, Token(userId, authToken)) tokenRepository.save(currentServer, Token(userId, authToken))
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
......
...@@ -2,6 +2,7 @@ package chat.rocket.android.authentication.resetpassword.presentation ...@@ -2,6 +2,7 @@ package chat.rocket.android.authentication.resetpassword.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.isEmail import chat.rocket.android.util.extensions.isEmail
...@@ -19,7 +20,7 @@ class ResetPasswordPresenter @Inject constructor( ...@@ -19,7 +20,7 @@ class ResetPasswordPresenter @Inject constructor(
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
factory: RocketChatClientFactory, factory: RocketChatClientFactory,
serverInteractor: GetCurrentServerInteractor serverInteractor: GetConnectingServerInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
......
...@@ -6,7 +6,7 @@ import chat.rocket.android.core.behaviours.showMessage ...@@ -6,7 +6,7 @@ import chat.rocket.android.core.behaviours.showMessage
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor import chat.rocket.android.server.domain.SaveConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extensions.isValidUrl import chat.rocket.android.util.extensions.isValidUrl
...@@ -16,7 +16,7 @@ import javax.inject.Inject ...@@ -16,7 +16,7 @@ import javax.inject.Inject
class ServerPresenter @Inject constructor(private val view: ServerView, class ServerPresenter @Inject constructor(private val view: ServerView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val serverInteractor: SaveCurrentServerInteractor, private val serverInteractor: SaveConnectingServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor, private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
factory: RocketChatClientFactory factory: RocketChatClientFactory
......
...@@ -22,7 +22,8 @@ class SignupPresenter @Inject constructor( ...@@ -22,7 +22,8 @@ class SignupPresenter @Inject constructor(
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServerInteractor: SaveCurrentServerInteractor,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
...@@ -60,6 +61,7 @@ class SignupPresenter @Inject constructor( ...@@ -60,6 +61,7 @@ class SignupPresenter @Inject constructor(
// TODO This function returns a user token so should we save it? // TODO This function returns a user token so should we save it?
retryIO("login") { client.login(username, password) } retryIO("login") { client.login(username, password) }
val me = retryIO("me") { client.me() } val me = retryIO("me") { client.me() }
saveCurrentServerInteractor.save(currentServer)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me) saveAccount(me)
registerPushToken() registerPushToken()
......
...@@ -25,7 +25,8 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -25,7 +25,8 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServerInteractor: SaveCurrentServerInteractor,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
...@@ -55,6 +56,7 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -55,6 +56,7 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
} }
val me = retryIO("me") { client.me() } val me = retryIO("me") { client.me() }
saveAccount(me) saveAccount(me)
saveCurrentServerInteractor.save(currentServer)
tokenRepository.save(server, token) tokenRepository.save(server, token)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
......
...@@ -32,17 +32,25 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -32,17 +32,25 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
setContentView(R.layout.activity_authentication) setContentView(R.layout.activity_authentication)
setTheme(R.style.AuthenticationTheme) setTheme(R.style.AuthenticationTheme)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
}
override fun onStart() {
super.onStart()
val deepLinkInfo = intent.getLoginDeepLinkInfo() val deepLinkInfo = intent.getLoginDeepLinkInfo()
launch(UI + job) { launch(UI + job) {
val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false) val newServer = intent.getBooleanExtra(INTENT_ADD_NEW_SERVER, false)
// if we got authenticateWithDeepLink information, pass true to newServer also // if we got authenticateWithDeepLink information, pass true to newServer also
presenter.loadCredentials(newServer || deepLinkInfo != null) { authenticated -> presenter.loadCredentials(newServer || deepLinkInfo != null) { authenticated ->
if (!authenticated) { if (!authenticated) {
showServerInput(savedInstanceState, deepLinkInfo) showServerInput(deepLinkInfo)
}
} }
} }
} }
override fun onStop() {
job.cancel()
super.onStop()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
...@@ -53,17 +61,12 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -53,17 +61,12 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
} }
} }
override fun onDestroy() {
job.cancel()
super.onDestroy()
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> { override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector return fragmentDispatchingAndroidInjector
} }
fun showServerInput(savedInstanceState: Bundle?, deepLinkInfo: LoginDeepLinkInfo?) { fun showServerInput(deepLinkInfo: LoginDeepLinkInfo?) {
addFragment("ServerFragment", R.id.fragment_container) { addFragment("ServerFragment", R.id.fragment_container, allowStateLoss = true) {
ServerFragment.newInstance(deepLinkInfo) ServerFragment.newInstance(deepLinkInfo)
} }
} }
......
package chat.rocket.android.chatroom.di package chat.rocket.android.chatroom.di
import android.arch.lifecycle.LifecycleOwner import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.chatroom.presentation.ChatRoomView import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatroom.ui.ChatRoomFragment import chat.rocket.android.chatroom.ui.ChatRoomFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
...@@ -12,20 +10,22 @@ import dagger.Provides ...@@ -12,20 +10,22 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class ChatRoomFragmentModule { class ChatRoomFragmentModule {
@Provides @Provides
@PerFragment
fun chatRoomView(frag: ChatRoomFragment): ChatRoomView { fun chatRoomView(frag: ChatRoomFragment): ChatRoomView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: ChatRoomFragment): LifecycleOwner { fun provideLifecycleOwner(frag: ChatRoomFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.chatroom.di package chat.rocket.android.chatroom.di
import chat.rocket.android.chatroom.ui.ChatRoomFragment import chat.rocket.android.chatroom.ui.ChatRoomFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class ChatRoomFragmentProvider { abstract class ChatRoomFragmentProvider {
@ContributesAndroidInjector(modules = [ChatRoomFragmentModule::class]) @ContributesAndroidInjector(modules = [ChatRoomFragmentModule::class])
@PerFragment
abstract fun provideChatRoomFragment(): ChatRoomFragment abstract fun provideChatRoomFragment(): ChatRoomFragment
} }
\ No newline at end of file
...@@ -812,7 +812,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -812,7 +812,7 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
if (typingStatusList.isNotEmpty()) { if (typingStatusList.isNotEmpty()) {
view.showTypingStatus(typingStatusList) view.showTypingStatus(typingStatusList.toList()) // copy typingStatusList
} else { } else {
view.hideTypingStatusView() view.hideTypingStatusView()
} }
......
...@@ -31,7 +31,7 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -31,7 +31,7 @@ interface ChatRoomView : LoadingView, MessageView {
* *
* @param usernameList The list of username to show. * @param usernameList The list of username to show.
*/ */
fun showTypingStatus(usernameList: ArrayList<String>) fun showTypingStatus(usernameList: List<String>)
/** /**
* Hides the typing status view. * Hides the typing status view.
......
...@@ -367,7 +367,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -367,7 +367,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
override fun showTypingStatus(usernameList: ArrayList<String>) { override fun showTypingStatus(usernameList: List<String>) {
ui { ui {
when (usernameList.size) { when (usernameList.size) {
1 -> { 1 -> {
......
...@@ -14,6 +14,7 @@ import androidx.core.text.color ...@@ -14,6 +14,7 @@ import androidx.core.text.color
import androidx.core.text.scale import androidx.core.text.scale
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.domain.MessageReply import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.helper.MessageHelper import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
...@@ -47,6 +48,7 @@ import okhttp3.HttpUrl ...@@ -47,6 +48,7 @@ import okhttp3.HttpUrl
import java.security.InvalidParameterException import java.security.InvalidParameterException
import javax.inject.Inject import javax.inject.Inject
@PerFragment
class ViewModelMapper @Inject constructor( class ViewModelMapper @Inject constructor(
private val context: Context, private val context: Context,
private val parser: MessageParser, private val parser: MessageParser,
...@@ -59,8 +61,8 @@ class ViewModelMapper @Inject constructor( ...@@ -59,8 +61,8 @@ class ViewModelMapper @Inject constructor(
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val settings: Map<String, Value<Any>> = getSettingsInteractor.get(currentServer) private val settings = getSettingsInteractor.get(currentServer)
private val baseUrl = settings.baseUrl() private val baseUrl = currentServer
private val token = tokenRepository.get(currentServer) private val token = tokenRepository.get(currentServer)
private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText) private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
......
package chat.rocket.android.chatrooms.di package chat.rocket.android.chatrooms.di
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class ChatRoomsFragmentProvider { abstract class ChatRoomsFragmentProvider {
@ContributesAndroidInjector(modules = [ChatRoomsFragmentModule::class]) @ContributesAndroidInjector(modules = [ChatRoomsFragmentModule::class])
@PerFragment
abstract fun provideChatRoomsFragment(): ChatRoomsFragment abstract fun provideChatRoomsFragment(): ChatRoomsFragment
} }
\ No newline at end of file
...@@ -3,6 +3,7 @@ package chat.rocket.android.chatrooms.ui ...@@ -3,6 +3,7 @@ package chat.rocket.android.chatrooms.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.renderscript.RSInvalidStateException
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.util.DiffUtil import android.support.v7.util.DiffUtil
...@@ -37,11 +38,13 @@ import chat.rocket.android.widget.DividerItemDecoration ...@@ -37,11 +38,13 @@ import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.core.internal.realtime.socket.model.State import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import com.crashlytics.android.Crashlytics
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.* import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.NonCancellable.isActive import kotlinx.coroutines.experimental.NonCancellable.isActive
import timber.log.Timber import timber.log.Timber
import java.security.InvalidParameterException
import javax.inject.Inject import javax.inject.Inject
private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID" private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID"
......
...@@ -14,6 +14,7 @@ import chat.rocket.android.app.RocketChatDatabase ...@@ -14,6 +14,7 @@ import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository
import chat.rocket.android.chatroom.service.MessageService import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.qualifier.ForAuthentication
import chat.rocket.android.dagger.qualifier.ForMessages import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
...@@ -23,6 +24,7 @@ import chat.rocket.android.push.PushManager ...@@ -23,6 +24,7 @@ import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.* import chat.rocket.android.server.infraestructure.*
import chat.rocket.android.util.AppJsonAdapterFactory import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.HttpLoggingInterceptor
import chat.rocket.android.util.TimberLogger import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date import chat.rocket.common.internal.ISO8601Date
...@@ -42,7 +44,6 @@ import dagger.Module ...@@ -42,7 +44,6 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import ru.noties.markwon.SpannableConfiguration import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.spans.SpannableTheme import ru.noties.markwon.spans.SpannableTheme
import timber.log.Timber import timber.log.Timber
...@@ -54,7 +55,9 @@ class AppModule { ...@@ -54,7 +55,9 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideRocketChatClient(okHttpClient: OkHttpClient, repository: TokenRepository, logger: PlatformLogger): RocketChatClient { fun provideRocketChatClient(okHttpClient: OkHttpClient,
repository: TokenRepository,
logger: PlatformLogger): RocketChatClient {
return RocketChatClient.create { return RocketChatClient.create {
httpClient = okHttpClient httpClient = okHttpClient
tokenRepository = repository tokenRepository = repository
...@@ -68,7 +71,8 @@ class AppModule { ...@@ -68,7 +71,8 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideRocketChatDatabase(context: Application): RocketChatDatabase { fun provideRocketChatDatabase(context: Application): RocketChatDatabase {
return Room.databaseBuilder(context.applicationContext, RocketChatDatabase::class.java, "rocketchat-db").build() return Room.databaseBuilder(context.applicationContext, RocketChatDatabase::class.java,
"rocketchat-db").build()
} }
@Provides @Provides
...@@ -91,8 +95,10 @@ class AppModule { ...@@ -91,8 +95,10 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message -> val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Timber.d(message) Timber.d(message)
}
}) })
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY interceptor.level = HttpLoggingInterceptor.Level.BODY
...@@ -168,6 +174,12 @@ class AppModule { ...@@ -168,6 +174,12 @@ class AppModule {
return SharedPrefsCurrentServerRepository(prefs) return SharedPrefsCurrentServerRepository(prefs)
} }
@Provides
@ForAuthentication
fun provideConnectingServerRepository(prefs: SharedPreferences): CurrentServerRepository {
return SharedPrefsConnectingServerRepository(prefs)
}
@Provides @Provides
@Singleton @Singleton
fun provideSettingsRepository(localRepository: LocalRepository): SettingsRepository { fun provideSettingsRepository(localRepository: LocalRepository): SettingsRepository {
......
package chat.rocket.android.dagger.qualifier
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class ForAuthentication
\ No newline at end of file
...@@ -2,10 +2,6 @@ package chat.rocket.android.dagger.qualifier ...@@ -2,10 +2,6 @@ package chat.rocket.android.dagger.qualifier
import javax.inject.Qualifier import javax.inject.Qualifier
/**
* Created by luciofm on 4/14/18.
*/
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class ForMessages annotation class ForMessages
\ No newline at end of file
package chat.rocket.android.chatroom.di package chat.rocket.android.chatroom.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.favoritemessages.ui.FavoriteMessagesFragment import chat.rocket.android.favoritemessages.ui.FavoriteMessagesFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class FavoriteMessagesFragmentProvider { abstract class FavoriteMessagesFragmentProvider {
@ContributesAndroidInjector(modules = [FavoriteMessagesFragmentModule::class]) @ContributesAndroidInjector(modules = [FavoriteMessagesFragmentModule::class])
@PerFragment
abstract fun provideFavoriteMessageFragment(): FavoriteMessagesFragment abstract fun provideFavoriteMessageFragment(): FavoriteMessagesFragment
} }
\ No newline at end of file
package chat.rocket.android.infrastructure
import android.app.Application
import chat.rocket.android.BuildConfig
import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.SITE_URL
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.experimental.runBlocking
fun installCrashlyticsWrapper(context: Application,
currentServerInteractor: GetCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor,
accountRepository: AccountsRepository,
localRepository: LocalRepository) {
if (isCrashlyticsEnabled()) {
Thread.setDefaultUncaughtExceptionHandler(RocketChatUncaughtExceptionHandler(currentServerInteractor,
settingsInteractor, accountRepository, localRepository))
}
}
private fun isCrashlyticsEnabled(): Boolean {
return !BuildConfig.DEBUG
}
private class RocketChatUncaughtExceptionHandler(
val currentServerInteractor: GetCurrentServerInteractor,
val settingsInteractor: GetSettingsInteractor,
val accountRepository: AccountsRepository,
val localRepository: LocalRepository)
: Thread.UncaughtExceptionHandler {
val crashlyticsHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(t: Thread, e: Throwable) {
val currentServer = currentServerInteractor.get() ?: "<unknown>"
Crashlytics.setString(KEY_CURRENT_SERVER, currentServer)
runBlocking {
val accounts = accountRepository.load()
Crashlytics.setString(KEY_ACCOUNTS, accounts.toString())
}
val settings = settingsInteractor.get(currentServer)
Crashlytics.setInt(KEY_SETTINGS_SIZE, settings.size)
val baseUrl = settings[SITE_URL]?.toString()
Crashlytics.setString(KEY_SETTINGS_BASE_URL, baseUrl)
val user = localRepository.getCurrentUser(currentServer)
Crashlytics.setString(KEY_CURRENT_USER, user?.toString())
Crashlytics.setString(KEY_CURRENT_USERNAME, localRepository.username())
if (crashlyticsHandler != null) {
crashlyticsHandler.uncaughtException(t, e)
} else {
throw RuntimeException("Missing default exception handler")
}
}
}
private const val KEY_CURRENT_SERVER = "CURRENT_SERVER"
private const val KEY_CURRENT_USER = "CURRENT_USER"
private const val KEY_CURRENT_USERNAME = "CURRENT_USERNAME"
private const val KEY_ACCOUNTS = "ACCOUNTS"
private const val KEY_SETTINGS_SIZE = "SETTINGS_SIZE"
private const val KEY_SETTINGS_BASE_URL = "SETTINGS_BASE_URL"
\ No newline at end of file
package chat.rocket.android.pinnedmessages.di package chat.rocket.android.pinnedmessages.di
import chat.rocket.android.chatroom.di.PinnedMessagesFragmentModule import chat.rocket.android.chatroom.di.PinnedMessagesFragmentModule
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.pinnedmessages.ui.PinnedMessagesFragment import chat.rocket.android.pinnedmessages.ui.PinnedMessagesFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -9,5 +10,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -9,5 +10,6 @@ import dagger.android.ContributesAndroidInjector
abstract class PinnedMessagesFragmentProvider { abstract class PinnedMessagesFragmentProvider {
@ContributesAndroidInjector(modules = [PinnedMessagesFragmentModule::class]) @ContributesAndroidInjector(modules = [PinnedMessagesFragmentModule::class])
@PerFragment
abstract fun providePinnedMessageFragment(): PinnedMessagesFragment abstract fun providePinnedMessageFragment(): PinnedMessagesFragment
} }
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.android.dagger.qualifier.ForAuthentication
import javax.inject.Inject
class GetConnectingServerInteractor @Inject constructor(
@ForAuthentication private val repository: CurrentServerRepository
) {
fun get(): String? = repository.get()
fun clear() {
repository.clear()
}
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.android.dagger.qualifier.ForAuthentication
import javax.inject.Inject
class SaveConnectingServerInteractor @Inject constructor(
@ForAuthentication private val repository: CurrentServerRepository
) {
fun save(url: String) = repository.save(url)
}
\ No newline at end of file
...@@ -5,11 +5,6 @@ import chat.rocket.core.model.Value ...@@ -5,11 +5,6 @@ import chat.rocket.core.model.Value
typealias PublicSettings = Map<String, Value<Any>> typealias PublicSettings = Map<String, Value<Any>>
interface SettingsRepository {
fun save(url: String, settings: PublicSettings)
fun get(url: String): PublicSettings
}
// Authentication methods. // Authentication methods.
const val LDAP_ENABLE = "LDAP_Enable" const val LDAP_ENABLE = "LDAP_Enable"
const val CAS_ENABLE = "CAS_enabled" const val CAS_ENABLE = "CAS_enabled"
...@@ -105,3 +100,8 @@ fun PublicSettings.uploadMaxFileSize(): Int { ...@@ -105,3 +100,8 @@ fun PublicSettings.uploadMaxFileSize(): Int {
fun PublicSettings.baseUrl(): String = this[SITE_URL]?.value as String fun PublicSettings.baseUrl(): String = this[SITE_URL]?.value as String
fun PublicSettings.siteName(): String? = this[SITE_NAME]?.value as String? fun PublicSettings.siteName(): String? = this[SITE_NAME]?.value as String?
interface SettingsRepository {
fun save(url: String, settings: PublicSettings)
fun get(url: String): PublicSettings
}
\ No newline at end of file
...@@ -5,6 +5,7 @@ import chat.rocket.android.infrastructure.LocalRepository.Companion.SETTINGS_KEY ...@@ -5,6 +5,7 @@ import chat.rocket.android.infrastructure.LocalRepository.Companion.SETTINGS_KEY
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.core.internal.SettingsAdapter import chat.rocket.core.internal.SettingsAdapter
import timber.log.Timber
class SharedPreferencesSettingsRepository( class SharedPreferencesSettingsRepository(
private val localRepository: LocalRepository private val localRepository: LocalRepository
...@@ -13,11 +14,27 @@ class SharedPreferencesSettingsRepository( ...@@ -13,11 +14,27 @@ class SharedPreferencesSettingsRepository(
private val adapter = SettingsAdapter().lenient() private val adapter = SettingsAdapter().lenient()
override fun save(url: String, settings: PublicSettings) { override fun save(url: String, settings: PublicSettings) {
if (settings.isEmpty()) {
val message = "Saving empty settings for $SETTINGS_KEY$url"
Timber.d(IllegalStateException(message), message)
}
localRepository.save("$SETTINGS_KEY$url", adapter.toJson(settings)) localRepository.save("$SETTINGS_KEY$url", adapter.toJson(settings))
} }
override fun get(url: String): PublicSettings { override fun get(url: String): PublicSettings {
val settings = localRepository.get("$SETTINGS_KEY$url") val settingsStr = localRepository.get("$SETTINGS_KEY$url")
return if (settings == null) hashMapOf() else adapter.fromJson(settings) ?: hashMapOf() return if (settingsStr == null) {
val message = "NULL Settings for: $SETTINGS_KEY$url"
Timber.d(IllegalStateException(message), message)
hashMapOf()
} else {
val settings = adapter.fromJson(settingsStr)
if (settings == null) {
val message = "NULL Settings for: $SETTINGS_KEY$url with saved settings: $settingsStr"
Timber.d(IllegalStateException(message), message)
}
settings ?: hashMapOf()
}
} }
} }
\ No newline at end of file
package chat.rocket.android.server.infraestructure
import android.content.SharedPreferences
import chat.rocket.android.server.domain.CurrentServerRepository
class SharedPrefsConnectingServerRepository(private val preferences: SharedPreferences) : CurrentServerRepository {
override fun save(url: String) {
preferences.edit().putString(CONNECTING_SERVER_KEY, url).apply()
}
override fun get(): String? {
return preferences.getString(CONNECTING_SERVER_KEY, null)
}
companion object {
private const val CONNECTING_SERVER_KEY = "connecting_server"
}
override fun clear() {
preferences.edit().remove(CONNECTING_SERVER_KEY).apply()
}
}
\ No newline at end of file
package chat.rocket.android.util
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.internal.http.HttpHeaders
import okhttp3.internal.platform.Platform
import okhttp3.internal.platform.Platform.INFO
import okio.Buffer
import okio.GzipSource
import java.io.EOFException
import java.io.IOException
import java.nio.charset.Charset
import java.util.concurrent.TimeUnit
/**
* An OkHttp interceptor which logs request and response information. Can be applied as an
* [application interceptor][OkHttpClient.interceptors] or as a [ ][OkHttpClient.networkInterceptors].
*
* The format of the logs created by
* this class should not be considered stable and may change slightly between releases. If you need
* a stable logging format, use your own interceptor.
*/
class HttpLoggingInterceptor constructor(private val logger: Logger) : Interceptor {
@Volatile
internal var level = Level.NONE
enum class Level {
/** No logs. */
NONE,
/**
* Logs request and response lines.
*
*
* Example:
* <pre>`--> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
`</pre> *
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
*
* Example:
* <pre>`--> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
`</pre> *
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
*
* Example:
* <pre>`--> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
`</pre> *
*/
BODY
}
interface Logger {
fun log(message: String)
}
/** Change the level at which this interceptor logs. */
fun setLevel(level: Level): HttpLoggingInterceptor {
this.level = level
return this
}
fun getLevel(): Level {
return level
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val level = this.level
val request = chain.request()
if (level == Level.NONE) {
return chain.proceed(request)
}
val logBody = level == Level.BODY
val logHeaders = logBody || level == Level.HEADERS
val requestBody = request.body()
val hasRequestBody = requestBody != null
val connection = chain.connection()
var requestStartMessage = ("--> ${request.method()} ${request.url()}"
+ if (connection != null) " " + connection.protocol() else "")
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (${requestBody!!.contentLength()}-byte body)"
}
logger.log(requestStartMessage)
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody!!.contentType() != null) {
logger.log("Content-Type: ${requestBody.contentType()!!}")
}
if (requestBody.contentLength() != -1L) {
logger.log("Content-Length: ${requestBody.contentLength()}")
}
}
val headers = request.headers()
var i = 0
val count = headers.size()
while (i < count) {
val name = headers.name(i)
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equals(name, ignoreCase = true) && !"Content-Length".equals(name, ignoreCase = true)) {
if ("X-Auth-Token".equals(name, ignoreCase = true)) {
logger.log("$name: ${skipAuthToken(headers.value(i).length)}")
} else {
logger.log("$name: ${headers.value(i)}")
}
}
i++
}
if (!logBody || !hasRequestBody) {
logger.log("--> END ${request.method()}")
} else if (bodyHasUnknownEncoding(request.headers())) {
logger.log("--> END ${request.method()} (encoded body omitted)")
} else if (isMultipart(requestBody?.contentType())) {//requestBody?.contentType()?.toString()?.contains("multipart/form-data", ignoreCase = true)) {
logger.log("--> END ${request.method()} (multipart body omitted)")
} else if (hasRequestBody) {
val buffer = Buffer()
requestBody!!.writeTo(buffer)
var charset: Charset? = UTF8
val contentType = requestBody.contentType()
if (contentType != null) {
charset = contentType.charset(UTF8)
}
logger.log("")
if (isPlaintext(buffer)) {
logger.log(buffer.readString(charset!!))
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)")
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)")
}
}
}
val startNs = System.nanoTime()
val response: Response
try {
response = chain.proceed(request)
} catch (e: Exception) {
logger.log("<-- HTTP FAILED: $e")
throw e
}
val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
val responseBody = response.body()
val contentLength = responseBody!!.contentLength()
val bodySize = if (contentLength != -1L) contentLength.toString() + "-byte" else "unknown-length"
val responseStr = if (response.message().isEmpty()) "" else " ${response.message()}"
logger.log("<-- ${response.code()}$responseStr ${response.request().url()}"
+ " (" + tookMs + "ms" + (if (!logHeaders) ", $bodySize body" else "") + ')'.toString())
if (logHeaders) {
val headers = response.headers()
var i = 0
val count = headers.size()
while (i < count) {
logger.log(headers.name(i) + ": " + headers.value(i))
i++
}
if (!logBody || !HttpHeaders.hasBody(response)) {
logger.log("<-- END HTTP")
} else if (bodyHasUnknownEncoding(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)")
} else {
val source = responseBody.source()
source.request(java.lang.Long.MAX_VALUE) // Buffer the entire body.
var buffer = source.buffer()
var gzippedLength: Long? = null
if (headers.get("Content-Encoding")?.equals("gzip", ignoreCase = true) == true) {
gzippedLength = buffer.size()
var gzippedResponseBody: GzipSource? = null
try {
gzippedResponseBody = GzipSource(buffer.clone())
buffer = Buffer()
buffer.writeAll(gzippedResponseBody)
} finally {
if (gzippedResponseBody != null) {
gzippedResponseBody.close()
}
}
}
var charset: Charset? = UTF8
val contentType = responseBody.contentType()
if (contentType != null) {
charset = contentType.charset(UTF8)
}
if (!isPlaintext(buffer)) {
logger.log("")
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)")
return response
}
if (contentLength != 0L) {
logger.log("")
logger.log(buffer.clone().readString(charset!!))
}
if (gzippedLength != null) {
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ gzippedLength + "-gzipped-byte body)")
} else {
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)")
}
}
}
return response
}
private fun isMultipart(contentType: MediaType?): Boolean {
return contentType?.let {
contentType.toString().contains("multipart/form-data")
} ?: false
}
private fun skipAuthToken(length: Int): String {
val builder = StringBuilder(length)
for (i in 1..length) builder.append("X")
return builder.toString()
}
private fun bodyHasUnknownEncoding(headers: Headers): Boolean {
val contentEncoding = headers.get("Content-Encoding")
return (contentEncoding != null
&& !contentEncoding.equals("identity", ignoreCase = true)
&& !contentEncoding.equals("gzip", ignoreCase = true))
}
companion object {
private val UTF8 = Charset.forName("UTF-8")
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
internal fun isPlaintext(buffer: Buffer): Boolean {
try {
val prefix = Buffer()
val byteCount = if (buffer.size() < 64) buffer.size() else 64
buffer.copyTo(prefix, 0, byteCount)
for (i in 0..15) {
if (prefix.exhausted()) {
break
}
val codePoint = prefix.readUtf8CodePoint()
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false
}
}
return true
} catch (e: EOFException) {
return false // Truncated UTF-8 sequence.
}
}
}
}
...@@ -31,11 +31,16 @@ fun View.isVisible(): Boolean { ...@@ -31,11 +31,16 @@ fun View.isVisible(): Boolean {
fun ViewGroup.inflate(@LayoutRes resource: Int, attachToRoot: Boolean = false): View = fun ViewGroup.inflate(@LayoutRes resource: Int, attachToRoot: Boolean = false): View =
LayoutInflater.from(context).inflate(resource, this, attachToRoot) LayoutInflater.from(context).inflate(resource, this, attachToRoot)
fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) { fun AppCompatActivity.addFragment(tag: String, layoutId: Int, allowStateLoss: Boolean = false,
newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance() val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
supportFragmentManager.beginTransaction() val transaction = supportFragmentManager.beginTransaction()
.replace(layoutId, fragment, tag) .replace(layoutId, fragment, tag)
.commit() if (allowStateLoss) {
transaction.commitAllowingStateLoss()
} else {
transaction.commit()
}
} }
fun AppCompatActivity.addFragmentBackStack( fun AppCompatActivity.addFragmentBackStack(
......
...@@ -5,12 +5,17 @@ import android.support.customtabs.CustomTabsIntent ...@@ -5,12 +5,17 @@ import android.support.customtabs.CustomTabsIntent
import android.support.v4.content.res.ResourcesCompat import android.support.v4.content.res.ResourcesCompat
import android.view.View import android.view.View
import chat.rocket.android.R import chat.rocket.android.R
import timber.log.Timber
fun View.openTabbedUrl(url: Uri) { fun View.openTabbedUrl(url: Uri) {
with(this) { with(this) {
val tabsbuilder = CustomTabsIntent.Builder() val tabsbuilder = CustomTabsIntent.Builder()
tabsbuilder.setToolbarColor(ResourcesCompat.getColor(context.resources, R.color.colorPrimary, context.theme)) tabsbuilder.setToolbarColor(ResourcesCompat.getColor(context.resources, R.color.colorPrimary, context.theme))
val customTabsIntent = tabsbuilder.build() val customTabsIntent = tabsbuilder.build()
try {
customTabsIntent.launchUrl(context, url) customTabsIntent.launchUrl(context, url)
} catch (ex: Exception) {
Timber.d(ex, "Unable to launch URL")
}
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment