Commit 6988897c authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Merge branch 'develop-2.x' into feature/show-and-update-user-profile

parents 9a5d9ad9 cf8c6513
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
...@@ -11,8 +12,8 @@ android { ...@@ -11,8 +12,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 1001 versionCode 1007
versionName "2.0.0-dev1" versionName "2.0.0-dev5.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
} }
...@@ -63,6 +64,7 @@ dependencies { ...@@ -63,6 +64,7 @@ dependencies {
implementation libraries.daggerSupport implementation libraries.daggerSupport
kapt libraries.daggerProcessor kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt kapt libraries.daggerAndroidApt
implementation libraries.playServicesGcm implementation libraries.playServicesGcm
implementation libraries.room implementation libraries.room
...@@ -81,6 +83,7 @@ dependencies { ...@@ -81,6 +83,7 @@ dependencies {
implementation libraries.rxBinding implementation libraries.rxBinding
implementation libraries.fresco implementation libraries.fresco
api libraries.frescoOkHttp
implementation libraries.frescoAnimatedGif implementation libraries.frescoAnimatedGif
implementation libraries.frescoWebP implementation libraries.frescoWebP
implementation libraries.frescoAnimatedWebP implementation libraries.frescoAnimatedWebP
...@@ -98,6 +101,10 @@ dependencies { ...@@ -98,6 +101,10 @@ dependencies {
implementation libraries.moshiLazyAdapters implementation libraries.moshiLazyAdapters
implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true
}
testImplementation libraries.junit testImplementation libraries.junit
androidTestImplementation(libraries.expressoCore, { androidTestImplementation(libraries.expressoCore, {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
......
...@@ -74,6 +74,11 @@ ...@@ -74,6 +74,11 @@
<action android:name="com.google.android.c2dm.intent.RECEIVE" /> <action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter> </intent-filter>
</service> </service>
<meta-data
android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -4,8 +4,15 @@ import android.app.Activity ...@@ -4,8 +4,15 @@ import android.app.Activity
import android.app.Application import android.app.Application
import android.app.Service import android.app.Service
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.app.utils.CustomImageFormatConfigurator
import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.helper.CrashlyticsTree
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.common.model.Token
import chat.rocket.core.TokenRepository
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore
import com.facebook.drawee.backends.pipeline.DraweeConfig 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
...@@ -14,6 +21,7 @@ import dagger.android.AndroidInjector ...@@ -14,6 +21,7 @@ import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector import dagger.android.HasActivityInjector
import dagger.android.HasServiceInjector import dagger.android.HasServiceInjector
import io.fabric.sdk.android.Fabric
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -25,32 +33,60 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -25,32 +33,60 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject @Inject
lateinit var serviceDispatchingAndroidInjector: DispatchingAndroidInjector<Service> lateinit var serviceDispatchingAndroidInjector: DispatchingAndroidInjector<Service>
@Inject
lateinit var imagePipelineConfig: ImagePipelineConfig
@Inject
lateinit var draweeConfig: DraweeConfig
// TODO - remove this from here when we have a proper service handling the connection.
@Inject
lateinit var getCurrentServerInteractor: GetCurrentServerInteractor
@Inject
lateinit var multiServerRepository: MultiServerTokenRepository
@Inject
lateinit var settingsRepository: SettingsRepository
@Inject
lateinit var tokenRepository: TokenRepository
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
DaggerAppComponent.builder().application(this).build().inject(this) DaggerAppComponent.builder().application(this).build().inject(this)
// TODO - remove this when we have a proper service handling connection...
initCurrentServer()
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
setupCrashlytics()
setupFresco() setupFresco()
setupTimber() setupTimber()
} }
private fun setupFresco() { // TODO - remove this when we have a proper service handling connection...
val imagePipelineConfig = ImagePipelineConfig.newBuilder(this) private fun initCurrentServer() {
.setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig()) val currentServer = getCurrentServerInteractor.get()
.build() val serverToken = currentServer?.let { multiServerRepository.get(currentServer) }
val settings = currentServer?.let { settingsRepository.get(currentServer) }
val draweeConfigBuilder = DraweeConfig.newBuilder() if (currentServer != null && serverToken != null && settings != null) {
tokenRepository.save(Token(serverToken.userId, serverToken.authToken))
}
}
CustomImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder) private fun setupCrashlytics() {
val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()
Fabric.with(this, Crashlytics.Builder().core(core).build())
}
Fresco.initialize(this, imagePipelineConfig, draweeConfigBuilder.build()) private fun setupFresco() {
Fresco.initialize(this, imagePipelineConfig, draweeConfig)
} }
private fun setupTimber() { private fun setupTimber() {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
} else {
Timber.plant(CrashlyticsTree())
} }
} }
......
package chat.rocket.android.authentication.di package chat.rocket.android.authentication.di
import android.content.Context import android.content.Context
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
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.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.MultiServerTokenRepository
import com.squareup.moshi.Moshi
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
...@@ -17,10 +13,4 @@ class AuthenticationModule { ...@@ -17,10 +13,4 @@ class AuthenticationModule {
@Provides @Provides
@PerActivity @PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context) fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context)
@Provides
@PerActivity
fun provideMultiServerTokenRepository(repository: LocalRepository, moshi: Moshi): MultiServerTokenRepository {
return SharedPreferencesMultiServerTokenRepository(repository, moshi)
}
} }
\ No newline at end of file
...@@ -3,6 +3,7 @@ package chat.rocket.android.authentication.infraestructure ...@@ -3,6 +3,7 @@ package chat.rocket.android.authentication.infraestructure
import chat.rocket.android.authentication.domain.model.TokenModel import chat.rocket.android.authentication.domain.model.TokenModel
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.LocalRepository.Companion.TOKEN_KEY
import chat.rocket.android.server.domain.MultiServerTokenRepository import chat.rocket.android.server.domain.MultiServerTokenRepository
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
...@@ -28,6 +29,7 @@ class SharedPreferencesMultiServerTokenRepository(private val repository: LocalR ...@@ -28,6 +29,7 @@ class SharedPreferencesMultiServerTokenRepository(private val repository: LocalR
repository.save("$TOKEN_KEY$server", adapter.toJson(token)) repository.save("$TOKEN_KEY$server", adapter.toJson(token))
} }
} override fun clear(server: String) {
repository.clear("$TOKEN_KEY$server")
const val TOKEN_KEY = "token_" }
\ No newline at end of file }
\ No newline at end of file
...@@ -17,7 +17,7 @@ import chat.rocket.android.helper.AnimationHelper ...@@ -17,7 +17,7 @@ import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.inflate import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility import chat.rocket.android.util.setVisible
import chat.rocket.android.util.textContent import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.* import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
...@@ -92,8 +92,8 @@ class LoginFragment : Fragment(), LoginView { ...@@ -92,8 +92,8 @@ class LoginFragment : Fragment(), LoginView {
override fun showOauthView(value: Boolean) { override fun showOauthView(value: Boolean) {
if (value) { if (value) {
social_accounts_container.setVisibility(true) social_accounts_container.setVisible(true)
button_fab.setVisibility(true) button_fab.setVisible(true)
// We need to setup the layout to hide and show the oauth interface when the soft keyboard is shown // We need to setup the layout to hide and show the oauth interface when the soft keyboard is shown
// (means that the user touched the text_username_or_email or text_password EditText to fill that respective fields). // (means that the user touched the text_username_or_email or text_password EditText to fill that respective fields).
...@@ -102,8 +102,8 @@ class LoginFragment : Fragment(), LoginView { ...@@ -102,8 +102,8 @@ class LoginFragment : Fragment(), LoginView {
isGlobalLayoutListenerSetUp = true isGlobalLayoutListenerSetUp = true
} }
} else { } else {
social_accounts_container.setVisibility(false) social_accounts_container.setVisible(false)
button_fab.setVisibility(false) button_fab.setVisible(false)
} }
} }
...@@ -143,7 +143,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -143,7 +143,7 @@ class LoginFragment : Fragment(), LoginView {
button_gitlab.isEnabled = true button_gitlab.isEnabled = true
} }
override fun showSignUpView(value: Boolean) = text_new_to_rocket_chat.setVisibility(value) override fun showSignUpView(value: Boolean) = text_new_to_rocket_chat.setVisible(value)
override fun alertWrongUsernameOrEmail() { override fun alertWrongUsernameOrEmail() {
AnimationHelper.vibrateSmartPhone(appContext) AnimationHelper.vibrateSmartPhone(appContext)
...@@ -159,11 +159,11 @@ class LoginFragment : Fragment(), LoginView { ...@@ -159,11 +159,11 @@ class LoginFragment : Fragment(), LoginView {
override fun showLoading() { override fun showLoading() {
enableUserInput(false) enableUserInput(false)
view_loading.setVisibility(true) view_loading.setVisible(true)
} }
override fun hideLoading() { override fun hideLoading() {
view_loading.setVisibility(false) view_loading.setVisible(false)
enableUserInput(true) enableUserInput(true)
} }
...@@ -189,7 +189,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -189,7 +189,7 @@ class LoginFragment : Fragment(), LoginView {
} }
private fun showLoginButton(value: Boolean) { private fun showLoginButton(value: Boolean) {
button_log_in.setVisibility(value) button_log_in.setVisible(value)
} }
private fun setupSignUpListener() { private fun setupSignUpListener() {
......
...@@ -48,11 +48,11 @@ class ServerFragment : Fragment(), ServerView { ...@@ -48,11 +48,11 @@ class ServerFragment : Fragment(), ServerView {
override fun showLoading() { override fun showLoading() {
enableUserInput(false) enableUserInput(false)
view_loading.setVisibility(true) view_loading.setVisible(true)
} }
override fun hideLoading() { override fun hideLoading() {
view_loading.setVisibility(false) view_loading.setVisible(false)
enableUserInput(true) enableUserInput(true)
} }
......
...@@ -14,7 +14,7 @@ import chat.rocket.android.authentication.signup.presentation.SignupView ...@@ -14,7 +14,7 @@ import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.AnimationHelper import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.setVisibility import chat.rocket.android.util.setVisible
import chat.rocket.android.util.textContent import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.* import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
...@@ -26,9 +26,9 @@ class SignupFragment : Fragment(), SignupView { ...@@ -26,9 +26,9 @@ class SignupFragment : Fragment(), SignupView {
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) { if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.setVisibility(false) text_new_user_agreement.setVisible(false)
} else { } else {
text_new_user_agreement.setVisibility(true) text_new_user_agreement.setVisible(true)
} }
} }
...@@ -92,11 +92,11 @@ class SignupFragment : Fragment(), SignupView { ...@@ -92,11 +92,11 @@ class SignupFragment : Fragment(), SignupView {
override fun showLoading() { override fun showLoading() {
enableUserInput(false) enableUserInput(false)
view_loading.setVisibility(true) view_loading.setVisible(true)
} }
override fun hideLoading() { override fun hideLoading() {
view_loading.setVisibility(false) view_loading.setVisible(false)
enableUserInput(true) enableUserInput(true)
} }
......
...@@ -14,7 +14,7 @@ import chat.rocket.android.R ...@@ -14,7 +14,7 @@ import chat.rocket.android.R
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.helper.AnimationHelper import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.util.setVisibility import chat.rocket.android.util.setVisible
import chat.rocket.android.util.textContent import chat.rocket.android.util.textContent
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.* import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
...@@ -75,11 +75,11 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -75,11 +75,11 @@ class TwoFAFragment : Fragment(), TwoFAView {
override fun showLoading() { override fun showLoading() {
enableUserInput(false) enableUserInput(false)
view_loading.setVisibility(true) view_loading.setVisible(true)
} }
override fun hideLoading() { override fun hideLoading() {
view_loading.setVisibility(false) view_loading.setVisible(false)
enableUserInput(true) enableUserInput(true)
} }
......
...@@ -4,14 +4,17 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -4,14 +4,17 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.model.BaseRoom import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.realtime.State
import chat.rocket.core.internal.realtime.connect
import chat.rocket.core.internal.realtime.subscribeRoomMessages import chat.rocket.core.internal.realtime.subscribeRoomMessages
import chat.rocket.core.internal.realtime.unsubscibre import chat.rocket.core.internal.realtime.unsubscibre
import chat.rocket.core.internal.rest.messages import chat.rocket.core.internal.rest.messages
import chat.rocket.core.internal.rest.sendMessage import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -19,23 +22,29 @@ import javax.inject.Inject ...@@ -19,23 +22,29 @@ import javax.inject.Inject
class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory,
private val mapper: MessageViewModelMapper) {
private val client = factory.create(serverInteractor.get()!!) private val client = factory.create(serverInteractor.get()!!)
private val roomMessages = ArrayList<Message>() private val roomMessages = ArrayList<Message>()
private var subId: String? = null private var subId: String? = null
fun loadMessages(chatRoomId: String, chatRoomType: String, offset: Int = 0) { private val stateChannel = Channel<State>()
fun loadMessages(chatRoomId: String, chatRoomType: String, offset: Long = 0) {
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
val messages = client.messages(chatRoomId, BaseRoom.RoomType.valueOf(chatRoomType), offset.toLong(), 30).result val messages = client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
if (messages != null) { synchronized(roomMessages) {
synchronized(roomMessages) { roomMessages.addAll(messages)
roomMessages.addAll(messages) }
}
view.showMessages(messages, serverInteractor.get()!!) val messagesViewModels = mapper.mapToViewModelList(messages, settings)
} else { view.showMessages(messagesViewModels, serverInteractor.get()!!)
view.showGenericErrorMessage()
// Subscribe after getting the first page of messages from REST
if (offset == 0L) {
subscribeMessages(chatRoomId)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
...@@ -71,16 +80,50 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -71,16 +80,50 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
} }
fun subscribeMessages(roomId: String) { fun subscribeMessages(roomId: String) {
client.addStateChannel(stateChannel)
launch(CommonPool + strategy.jobs) {
for (status in stateChannel) {
Timber.d("Changing status to: $status")
when (status) {
State.Authenticating -> Timber.d("Authenticating")
State.Connected -> {
Timber.d("Connected")
subId = client.subscribeRoomMessages(roomId) {
Timber.d("subscribe messages for $roomId: $it")
}
}
}
}
Timber.d("Done on statusChannel")
}
when (client.state) {
State.Connected -> {
Timber.d("Already connected")
subId = client.subscribeRoomMessages(roomId) {
Timber.d("subscribe messages for $roomId: $it")
}
}
else -> client.connect()
}
launchUI(strategy) { launchUI(strategy) {
listenMessages(roomId)
}
// TODO - when we have a proper service, we won't need to take care of connection, just
// subscribe and listen...
/*launchUI(strategy) {
subId = client.subscribeRoomMessages(roomId) { subId = client.subscribeRoomMessages(roomId) {
Timber.d("subscribe messages for $roomId: $it") Timber.d("subscribe messages for $roomId: $it")
} }
listenMessages(roomId) listenMessages(roomId)
} }*/
} }
fun unsubscribeMessages() { fun unsubscribeMessages() {
launch(CommonPool) { launch(CommonPool) {
client.removeStateChannel(stateChannel)
subId?.let { subscriptionId -> subId?.let { subscriptionId ->
client.unsubscibre(subscriptionId) client.unsubscibre(subscriptionId)
} }
...@@ -101,16 +144,17 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -101,16 +144,17 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private fun updateMessage(streamedMessage: Message) { private fun updateMessage(streamedMessage: Message) {
launchUI(strategy) { launchUI(strategy) {
val viewModelStreamedMessage = mapper.mapToViewModel(streamedMessage, settings)
synchronized(roomMessages) { synchronized(roomMessages) {
val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id } val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id }
if (index != -1) { if (index != -1) {
Timber.d("Updatind message at $index") Timber.d("Updatind message at $index")
roomMessages[index] = streamedMessage roomMessages[index] = streamedMessage
view.dispatchUpdateMessage(index, streamedMessage) view.dispatchUpdateMessage(index, viewModelStreamedMessage)
} else { } else {
Timber.d("Adding new message") Timber.d("Adding new message")
roomMessages.add(0, streamedMessage) roomMessages.add(0, streamedMessage)
view.showNewMessage(streamedMessage) view.showNewMessage(viewModelStreamedMessage)
} }
} }
} }
......
package chat.rocket.android.chatroom.ui package chat.rocket.android.chatroom.ui
import DateTimeHelper
import android.content.Context
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.chatroom.viewmodel.AttachmentType
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.inflate import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility import chat.rocket.android.util.setVisible
import chat.rocket.android.util.textContent
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.model.Message
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_message.view.* import kotlinx.android.synthetic.main.item_message.view.*
import kotlinx.android.synthetic.main.message_attachment.view.*
class ChatRoomAdapter(private val context: Context, class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() {
private val serverUrl: String) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() {
init { init {
setHasStableIds(true) setHasStableIds(true)
} }
val dataSet = ArrayList<Message>() val dataSet = ArrayList<MessageViewModel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_message)) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(parent.inflate(R.layout.item_message), serverUrl)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position]) override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position])
...@@ -39,18 +39,18 @@ class ChatRoomAdapter(private val context: Context, ...@@ -39,18 +39,18 @@ class ChatRoomAdapter(private val context: Context,
override fun getItemViewType(position: Int): Int = position override fun getItemViewType(position: Int): Int = position
fun addDataSet(dataSet: List<Message>) { fun addDataSet(dataSet: List<MessageViewModel>) {
val previousDataSetSize = this.dataSet.size val previousDataSetSize = this.dataSet.size
this.dataSet.addAll(previousDataSetSize, dataSet) this.dataSet.addAll(previousDataSetSize, dataSet)
notifyItemRangeInserted(previousDataSetSize, dataSet.size) notifyItemRangeInserted(previousDataSetSize, dataSet.size)
} }
fun addItem(message: Message) { fun addItem(message: MessageViewModel) {
dataSet.add(0, message) dataSet.add(0, message)
notifyItemInserted(0) notifyItemInserted(0)
} }
fun updateItem(index: Int, message: Message) { fun updateItem(index: Int, message: MessageViewModel) {
dataSet[index] = message dataSet[index] = message
notifyItemChanged(index) notifyItemChanged(index)
} }
...@@ -59,33 +59,68 @@ class ChatRoomAdapter(private val context: Context, ...@@ -59,33 +59,68 @@ class ChatRoomAdapter(private val context: Context,
return dataSet[position].id.hashCode().toLong() return dataSet[position].id.hashCode().toLong()
} }
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class ViewHolder(itemView: View, val serverUrl: String) : RecyclerView.ViewHolder(itemView) {
fun bind(message: Message) = with(itemView) { fun bind(message: MessageViewModel) = with(itemView) {
bindUserAvatar(message, image_avatar, image_unknown_avatar) bindUserAvatar(message, image_avatar, image_unknown_avatar)
bindUserName(message, text_user_name) text_user_name.text = message.sender
bindTime(message, text_message_time) text_message_time.text = message.time
bindContent(message, text_content) text_content.text = message.content
}
private fun bindUserAvatar(message: Message, drawee: SimpleDraweeView, imageUnknownAvatar: ImageView) = message.sender?.username.let {
drawee.setImageURI(UrlHelper.getAvatarUrl(serverUrl, it.toString()))
}.ifNull {
imageUnknownAvatar.setVisibility(true)
}
private fun bindUserName(message: Message, textView: TextView) = message.sender?.username.let { bindAttachment(message, message_attachment, image_attachment, audio_video_attachment,
textView.textContent = it.toString() file_name)
}.ifNull {
textView.textContent = context.getString(R.string.msg_unknown)
} }
private fun bindTime(message: Message, textView: TextView) { private fun bindAttachment(message: MessageViewModel,
textView.textContent = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(message.timestamp)) attachment_container: View,
image_attachment: SimpleDraweeView,
audio_video_attachment: View,
file_name: TextView) {
with(message) {
if (attachmentUrl == null || attachmentType == null) {
attachment_container.setVisible(false)
return
}
var imageVisible = false
var videoVisible = false
attachment_container.setVisible(true)
when (message.attachmentType) {
is AttachmentType.Image -> {
imageVisible = true
image_attachment.setImageURI(message.attachmentUrl)
image_attachment.setOnClickListener { view ->
// TODO - implement a proper image viewer with a proper Transition
ImageViewer.Builder(view.context, listOf(message.attachmentUrl))
.setStartPosition(0)
.show()
}
}
is AttachmentType.Video,
is AttachmentType.Audio -> {
videoVisible = true
audio_video_attachment.setOnClickListener { view ->
message.attachmentUrl?.let { url ->
PlayerActivity.play(view.context, url)
}
}
}
}
image_attachment.setVisible(imageVisible)
audio_video_attachment.setVisible(videoVisible)
file_name.text = message.attachmentTitle
}
} }
private fun bindContent(message: Message, textView: TextView) { private fun bindUserAvatar(message: MessageViewModel, drawee: SimpleDraweeView, imageUnknownAvatar: ImageView) = message.getAvatarUrl(serverUrl).let {
textView.textContent = message.message drawee.setImageURI(it.toString())
drawee.setVisible(true)
imageUnknownAvatar.setVisible(false)
}.ifNull {
drawee.setVisible(false)
imageUnknownAvatar.setVisible(true)
} }
} }
} }
\ No newline at end of file
...@@ -14,7 +14,7 @@ import chat.rocket.android.chatroom.presentation.ChatRoomPresenter ...@@ -14,7 +14,7 @@ import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.presentation.ChatRoomView import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.inflate import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility import chat.rocket.android.util.setVisible
import chat.rocket.android.util.textContent import chat.rocket.android.util.textContent
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
...@@ -67,7 +67,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -67,7 +67,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
presenter.loadMessages(chatRoomId, chatRoomType) presenter.loadMessages(chatRoomId, chatRoomType)
presenter.subscribeMessages(chatRoomId)
setupComposer() setupComposer()
} }
...@@ -87,7 +86,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -87,7 +86,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
if (dataSet.size >= 30) { if (dataSet.size >= 30) {
recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) { recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) { override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
presenter.loadMessages(chatRoomId, chatRoomType, page * 30) presenter.loadMessages(chatRoomId, chatRoomType, page * 30L)
} }
}) })
} }
...@@ -124,9 +123,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -124,9 +123,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
adapter.updateItem(index, message) adapter.updateItem(index, message)
} }
override fun showLoading() = view_loading.setVisibility(true) override fun showLoading() = view_loading.setVisible(true)
override fun hideLoading() = view_loading.setVisibility(false) override fun hideLoading() = view_loading.setVisible(false)
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show() override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
...@@ -134,8 +133,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -134,8 +133,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
private fun setupComposer() { private fun setupComposer() {
if (isChatRoomReadOnly) { if (isChatRoomReadOnly) {
text_room_is_read_only.setVisibility(true) text_room_is_read_only.setVisible(true)
top_container.setVisibility(false) top_container.setVisible(false)
} else { } else {
text_send.setOnClickListener { sendMessage(text_message.textContent) } text_send.setOnClickListener { sendMessage(text_message.textContent) }
} }
......
package chat.rocket.android.chatroom.viewmodel
import DateTimeHelper
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.USE_REALNAME
import chat.rocket.common.model.Token
import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType.*
import chat.rocket.core.model.Value
import chat.rocket.core.model.attachment.AudioAttachment
import chat.rocket.core.model.attachment.FileAttachment
import chat.rocket.core.model.attachment.ImageAttachment
import chat.rocket.core.model.attachment.VideoAttachment
import okhttp3.HttpUrl
data class MessageViewModel(val context: Context,
private val token: Token?,
private val message: Message,
private val settings: Map<String, Value<Any>>?) {
val id: String = message.id
val time: CharSequence
val sender: CharSequence
val content: CharSequence
var attachmentUrl: String? = null
var attachmentTitle: CharSequence? = null
var attachmentType: AttachmentType? = null
init {
sender = getSenderName()
content = getContent(context)
time = getTime()
message.attachments?.let {
if (it.isEmpty() || it[0] == null) return@let
val attachment = it[0] as FileAttachment
val baseUrl = settings?.get(SITE_URL)
baseUrl?.let {
attachmentUrl = attachmentUrl("${baseUrl.value}${attachment.url}")
attachmentTitle = attachment.title
attachmentType = when (attachment) {
is ImageAttachment -> AttachmentType.Image
is VideoAttachment -> AttachmentType.Video
is AudioAttachment -> AttachmentType.Audio
else -> null
}
}
}
}
fun getAvatarUrl(serverUrl: String): String? {
return message.sender?.username.let {
return@let UrlHelper.getAvatarUrl(serverUrl, it.toString())
}
}
fun getTime() = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(message.timestamp))
fun getSenderName(): CharSequence {
val useRealName = settings?.get(USE_REALNAME)?.value as Boolean
val username = message.sender?.username
val realName = message.sender?.name
val senderName = if (useRealName) realName else username
return senderName ?: username.toString()
}
fun getContent(context: Context): CharSequence {
val contentMessage: CharSequence
when (message.type) {
//TODO: Add implementation for Welcome type.
is MessageRemoved -> contentMessage = getSystemMessage(context.getString(R.string.message_removed))
is UserJoined -> contentMessage = getSystemMessage(context.getString(R.string.message_user_joined_channel))
is UserLeft -> contentMessage = getSystemMessage(context.getString(R.string.message_user_left))
is UserAdded -> contentMessage = getSystemMessage(
context.getString(R.string.message_user_added_by, message.message, message.sender?.username))
is RoomNameChanged -> contentMessage = getSystemMessage(
context.getString(R.string.message_room_name_changed, message.message, message.sender?.username))
is UserRemoved -> contentMessage = getSystemMessage(
context.getString(R.string.message_user_removed_by, message.message, message.sender?.username))
else -> contentMessage = getNormalMessage()
}
return contentMessage
}
private fun getNormalMessage() = message.message
private fun getSystemMessage(content: String): CharSequence {
val spannableMsg = SpannableString(content)
spannableMsg.setSpan(StyleSpan(Typeface.ITALIC), 0, spannableMsg.length,
0)
spannableMsg.setSpan(ForegroundColorSpan(Color.GRAY), 0, spannableMsg.length,
0)
val username = message.sender?.username
val message = message.message
val usernameTextStartIndex = if (username != null) content.indexOf(username) else -1
val usernameTextEndIndex = if (username != null) usernameTextStartIndex + username.length else -1
val messageTextStartIndex = if (message.isNotEmpty()) content.indexOf(message) else -1
val messageTextEndIndex = messageTextStartIndex + message.length
if (usernameTextStartIndex > -1) {
spannableMsg.setSpan(StyleSpan(Typeface.BOLD_ITALIC), usernameTextStartIndex, usernameTextEndIndex,
0)
}
if (messageTextStartIndex > -1) {
spannableMsg.setSpan(StyleSpan(Typeface.BOLD_ITALIC), messageTextStartIndex, messageTextEndIndex,
0)
}
return spannableMsg
}
private fun attachmentUrl(url: String): String {
var response = url
val httpUrl = HttpUrl.parse(url)
httpUrl?.let {
response = it.newBuilder().apply {
addQueryParameter("rc_uid", token?.userId)
addQueryParameter("rc_token", token?.authToken)
}.build().toString()
}
return response
}
}
sealed class AttachmentType {
object Image : AttachmentType()
object Video : AttachmentType()
object Audio : AttachmentType()
}
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel
import android.content.Context
import chat.rocket.core.TokenRepository
import chat.rocket.core.model.Message
import chat.rocket.core.model.Value
import javax.inject.Inject
class MessageViewModelMapper @Inject constructor(private val context: Context, private val tokenRepository: TokenRepository) {
suspend fun mapToViewModel(message: Message, settings: Map<String, Value<Any>>?) = MessageViewModel(context, tokenRepository.get(), message, settings)
suspend fun mapToViewModelList(messageList: List<Message>, settings: Map<String, Value<Any>>?): List<MessageViewModel> {
return messageList.map { MessageViewModel(context, tokenRepository.get(), it, settings) }
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetChatRoomsInteractor import chat.rocket.android.server.domain.GetChatRoomsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SaveChatRoomsInteractor import chat.rocket.android.server.domain.SaveChatRoomsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.model.Subscription import chat.rocket.core.internal.model.Subscription
import chat.rocket.core.internal.realtime.* import chat.rocket.core.internal.realtime.*
import chat.rocket.core.internal.rest.chatRooms import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.internal.rest.logout
import chat.rocket.core.internal.rest.unregisterPushToken
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.Room import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.Channel
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: ChatRoomsNavigator, private val navigator: ChatRoomsNavigator,
serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val getChatRoomsInteractor: GetChatRoomsInteractor, private val getChatRoomsInteractor: GetChatRoomsInteractor,
private val saveChatRoomsInteractor: SaveChatRoomsInteractor, private val saveChatRoomsInteractor: SaveChatRoomsInteractor,
private val localRepository: LocalRepository,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory) {
private val client: RocketChatClient = factory.create(serverInteractor.get()!!) private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private var reloadJob: Deferred<List<ChatRoom>?>? = null private var reloadJob: Deferred<List<ChatRoom>>? = null
private val stateChannel = Channel<State>()
fun loadChatRooms() { fun loadChatRooms() {
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { view.updateChatRooms(loadRooms())
val chatRooms = getChatRooms() subscribeRoomUpdates()
if (chatRooms != null) {
view.updateChatRooms(chatRooms)
subscribeRoomUpdates()
} else {
view.showNoChatRoomsToDisplay()
}
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
view.hideLoading() view.hideLoading()
} }
} }
fun loadChatRoom(chatRoom: ChatRoom) { fun loadChatRoom(chatRoom: ChatRoom) = navigator.toChatRoom(chatRoom.id, chatRoom.name,
navigator.toChatRoom(chatRoom.id, chatRoom.type.toString(), chatRoom.readonly ?: false)
chatRoom.name,
chatRoom.type.name,
chatRoom.readonly ?: false)
}
/** /**
* Gets a [ChatRoom] list filtered by name from local repository. * Gets a [ChatRoom] list from local repository.
* * ChatRooms returned are filtered by name.
* @param name The Chat Room name to get.
*/ */
fun chatRoomsByName(name: String) { fun chatRoomsByName(name: String) {
val currentServer = serverInteractor.get()!!
launchUI(strategy) { launchUI(strategy) {
val roomList = getChatRoomsInteractor.getByName(currentServer, name) val roomList = getChatRoomsInteractor.getByName(currentServer, name)
view.updateChatRooms(roomList) view.updateChatRooms(roomList)
} }
} }
private suspend fun getChatRooms(): List<ChatRoom>? { private suspend fun loadRooms(): List<ChatRoom> {
val chatRooms = client.chatRooms().update val chatRooms = client.chatRooms().update
if (chatRooms != null) { val sortedRooms = sortRooms(chatRooms)
val sortedOpenChatRooms = sortOpenChatRooms(chatRooms) saveChatRoomsInteractor.save(currentServer, sortedRooms)
saveChatRoomsInteractor.save(currentServer, sortedOpenChatRooms) return sortedRooms
return sortedOpenChatRooms
}
return null
} }
private fun sortOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> { private fun sortRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
val openChatRooms = getOpenChatRooms(chatRooms) val openChatRooms = getOpenChatRooms(chatRooms)
return sortChatRooms(openChatRooms) return sortChatRooms(openChatRooms)
} }
private fun updateRooms() {
launch {
view.updateChatRooms(getChatRoomsInteractor.get(currentServer))
}
}
private fun getOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> { private fun getOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.filter(ChatRoom::open) return chatRooms.filter(ChatRoom::open)
} }
...@@ -94,16 +86,12 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -94,16 +86,12 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
chatRoom.lastMessage?.timestamp chatRoom.lastMessage?.timestamp
} }
} }
private fun updateChatRooms() {
launch {
view.updateChatRooms(getChatRoomsInteractor.get(currentServer))
}
}
// TODO - Temporary stuff, remove when adding DB support // TODO - Temporary stuff, remove when adding DB support
private suspend fun subscribeRoomUpdates() { private suspend fun subscribeRoomUpdates() {
client.addStateChannel(stateChannel)
launch(CommonPool + strategy.jobs) { launch(CommonPool + strategy.jobs) {
for (status in client.statusChannel) { for (status in stateChannel) {
Timber.d("Changing status to: $status") Timber.d("Changing status to: $status")
when (status) { when (status) {
State.Authenticating -> Timber.d("Authenticating") State.Authenticating -> Timber.d("Authenticating")
...@@ -159,7 +147,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -159,7 +147,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
} }
} }
updateChatRooms() updateRooms()
} }
} }
...@@ -179,7 +167,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -179,7 +167,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
} }
} }
updateChatRooms() updateRooms()
} }
} }
...@@ -190,7 +178,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -190,7 +178,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
reloadJob = async(CommonPool + strategy.jobs) { reloadJob = async(CommonPool + strategy.jobs) {
delay(1000) delay(1000)
Timber.d("reloading rooms after wait") Timber.d("reloading rooms after wait")
getChatRooms() loadRooms()
} }
reloadJob?.await() reloadJob?.await()
} }
...@@ -221,7 +209,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -221,7 +209,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
client) client)
removeRoom(room.id, chatRooms) removeRoom(room.id, chatRooms)
chatRooms.add(newRoom) chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortOpenChatRooms(chatRooms)) saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
} }
} }
...@@ -251,7 +239,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -251,7 +239,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
client) client)
removeRoom(subscription.roomId, chatRooms) removeRoom(subscription.roomId, chatRooms)
chatRooms.add(newRoom) chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortOpenChatRooms(chatRooms)) saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
} }
} }
...@@ -261,8 +249,39 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -261,8 +249,39 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
synchronized(this) { synchronized(this) {
chatRooms.removeAll { chatRoom -> chatRoom.id == id } chatRooms.removeAll { chatRoom -> chatRoom.id == id }
} }
saveChatRoomsInteractor.save(currentServer, sortOpenChatRooms(chatRooms)) saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
fun disconnect() {
client.removeStateChannel(stateChannel)
client.disconnect()
}
/**
* Logout from current server.
*/
fun logout() {
launchUI(strategy) {
try {
clearTokens()
client.logout()
//TODO: Add the code to unsubscribe to all subscriptions.
client.disconnect()
view.onLogout()
} catch (e: RocketChatException) {
Timber.e(e)
view.showMessage(e.message!!)
}
}
} }
fun disconnect() = client.disconnect() private suspend fun clearTokens() {
serverInteractor.clear()
val pushToken = localRepository.get(LocalRepository.KEY_PUSH_TOKEN)
if (pushToken != null) {
client.unregisterPushToken(pushToken)
localRepository.clear(LocalRepository.KEY_PUSH_TOKEN)
}
localRepository.clearAllFromServer(currentServer)
}
} }
\ No newline at end of file
...@@ -17,4 +17,9 @@ interface ChatRoomsView : LoadingView, MessageView { ...@@ -17,4 +17,9 @@ interface ChatRoomsView : LoadingView, MessageView {
* Shows no chat rooms to display. * Shows no chat rooms to display.
*/ */
fun showNoChatRoomsToDisplay() fun showNoChatRoomsToDisplay()
/**
* User has successfully logged out from the current server.
**/
fun onLogout()
} }
\ No newline at end of file
...@@ -11,9 +11,9 @@ import android.widget.TextView ...@@ -11,9 +11,9 @@ import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.util.inflate import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility import chat.rocket.android.util.setVisible
import chat.rocket.android.util.textContent import chat.rocket.android.util.textContent
import chat.rocket.common.model.BaseRoom.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.avatar.view.*
...@@ -50,14 +50,14 @@ class ChatRoomsAdapter(private val context: Context, ...@@ -50,14 +50,14 @@ class ChatRoomsAdapter(private val context: Context,
private fun bindAvatar(chatRoom: ChatRoom, avatarLayout: View, drawee: SimpleDraweeView, imageView: ImageView) { private fun bindAvatar(chatRoom: ChatRoom, avatarLayout: View, drawee: SimpleDraweeView, imageView: ImageView) {
val chatRoomName = chatRoom.name val chatRoomName = chatRoom.name
if (chatRoom.type == RoomType.ONE_TO_ONE) { if (chatRoom.type is RoomType.DirectMessage) {
drawee.setImageURI(UrlHelper.getAvatarUrl(chatRoom.client.url, chatRoomName)) drawee.setImageURI(UrlHelper.getAvatarUrl(chatRoom.client.url, chatRoomName))
imageView.setVisibility(false) imageView.setVisible(false)
avatarLayout.setVisibility(true) avatarLayout.setVisible(true)
} else { } else {
imageView.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName)) imageView.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName))
avatarLayout.setVisibility(false) avatarLayout.setVisible(false)
imageView.setVisibility(true) imageView.setVisible(true)
} }
} }
...@@ -99,11 +99,11 @@ class ChatRoomsAdapter(private val context: Context, ...@@ -99,11 +99,11 @@ class ChatRoomsAdapter(private val context: Context,
when { when {
totalUnreadMessage in 1..99 -> { totalUnreadMessage in 1..99 -> {
textView.textContent = totalUnreadMessage.toString() textView.textContent = totalUnreadMessage.toString()
textView.setVisibility(true) textView.setVisible(true)
} }
totalUnreadMessage > 99 -> { totalUnreadMessage > 99 -> {
textView.textContent = context.getString(R.string.msg_more_than_ninety_nine_unread_messages) textView.textContent = context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
textView.setVisibility(true) textView.setVisible(true)
} }
} }
} }
......
package chat.rocket.android.chatrooms.ui package chat.rocket.android.chatrooms.ui
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
...@@ -10,14 +11,15 @@ import android.support.v7.widget.SearchView ...@@ -10,14 +11,15 @@ import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.util.inflate import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.setVisible
import chat.rocket.android.widget.DividerItemDecoration import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_chat_rooms.* import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
...@@ -71,6 +73,13 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -71,6 +73,13 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}) })
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.action_logout -> presenter.logout()
}
return true
}
override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) { override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) {
activity.apply { activity.apply {
launch(UI) { launch(UI) {
...@@ -89,12 +98,23 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -89,12 +98,23 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun showLoading() = view_loading.setVisibility(true) override fun showLoading() = view_loading.setVisibility(true)
override fun hideLoading() = view_loading.setVisibility(false) override fun showLoading() = view_loading.setVisible(true)
override fun hideLoading() = view_loading.setVisible(false)
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show() override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun onLogout() {
activity?.apply {
finish()
val intent = Intent(this, AuthenticationActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}
private fun setupToolbar() { private fun setupToolbar() {
(activity as AppCompatActivity).supportActionBar?.title = getString(R.string.title_chats) (activity as AppCompatActivity).supportActionBar?.title = getString(R.string.title_chats)
} }
......
...@@ -6,11 +6,16 @@ import android.content.Context ...@@ -6,11 +6,16 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.app.RocketChatDatabase import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.app.utils.CustomImageFormatConfigurator
import chat.rocket.android.authentication.infraestructure.MemoryTokenRepository import chat.rocket.android.authentication.infraestructure.MemoryTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.dagger.qualifier.ForFresco
import chat.rocket.android.helper.FrescoAuthInterceptor
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPrefsLocalRepository import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.server.domain.ChatRoomsRepository import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.ServerDao import chat.rocket.android.server.infraestructure.ServerDao
...@@ -21,12 +26,19 @@ import chat.rocket.android.util.TimberLogger ...@@ -21,12 +26,19 @@ import chat.rocket.android.util.TimberLogger
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.TokenRepository import chat.rocket.core.TokenRepository
import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.facebook.imagepipeline.listener.RequestListener
import com.facebook.imagepipeline.listener.RequestLoggingListener
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
...@@ -71,11 +83,14 @@ class AppModule { ...@@ -71,11 +83,14 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor() val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message ->
Timber.d(message)
})
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY interceptor.level = HttpLoggingInterceptor.Level.BODY
} else { } else {
interceptor.level = HttpLoggingInterceptor.Level.HEADERS // TODO - change to HEADERS on production...
interceptor.level = HttpLoggingInterceptor.Level.BODY
} }
return interceptor return interceptor
...@@ -89,6 +104,46 @@ class AppModule { ...@@ -89,6 +104,46 @@ class AppModule {
}.build() }.build()
} }
@Provides
@ForFresco
@Singleton
fun provideFrescoAuthIntercepter(tokenRepository: TokenRepository): Interceptor {
return FrescoAuthInterceptor(tokenRepository)
}
@Provides
@ForFresco
@Singleton
fun provideFrescoOkHttpClient(okHttpClient: OkHttpClient, @ForFresco authInterceptor: Interceptor): OkHttpClient {
return okHttpClient.newBuilder().apply {
//addInterceptor(authInterceptor)
}.build()
}
@Provides
@Singleton
fun provideImagePipelineConfig(context: Context, @ForFresco okHttpClient: OkHttpClient): ImagePipelineConfig {
val listeners = HashSet<RequestListener>()
listeners.add(RequestLoggingListener())
return OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient)
.setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
.setRequestListeners(listeners)
.setDownsampleEnabled(true)
//.experiment().setBitmapPrepareToDraw(true).experiment()
.experiment().setPartialImageCachingEnabled(true).build()
}
@Provides
@Singleton
fun provideDraweeConfig(): DraweeConfig {
val draweeConfigBuilder = DraweeConfig.newBuilder()
CustomImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder)
return draweeConfigBuilder.build()
}
@Provides @Provides
@Singleton @Singleton
fun provideTokenRepository(): TokenRepository { fun provideTokenRepository(): TokenRepository {
...@@ -135,4 +190,10 @@ class AppModule { ...@@ -135,4 +190,10 @@ class AppModule {
fun provideMoshi(): Moshi { fun provideMoshi(): Moshi {
return Moshi.Builder().add(AppJsonAdapterFactory.INSTANCE).build() return Moshi.Builder().add(AppJsonAdapterFactory.INSTANCE).build()
} }
@Provides
@Singleton
fun provideMultiServerTokenRepository(repository: LocalRepository, moshi: Moshi): MultiServerTokenRepository {
return SharedPreferencesMultiServerTokenRepository(repository, moshi)
}
} }
\ No newline at end of file
package chat.rocket.android.dagger.qualifier
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class ForFresco
\ No newline at end of file
package chat.rocket.android.helper
import com.crashlytics.android.Crashlytics
import timber.log.Timber
class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String?, throwable: Throwable?) {
Crashlytics.log(priority, tag, message)
if (throwable != null) {
Crashlytics.logException(throwable)
}
}
}
\ No newline at end of file
package chat.rocket.android.helper
import chat.rocket.core.TokenRepository
import okhttp3.Interceptor
import okhttp3.Response
class FrescoAuthInterceptor(private val tokenRepository: TokenRepository) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenRepository.get()
var request = chain.request()
token?.let {
val url = request.url().newBuilder().apply {
addQueryParameter("rc_uid", token.userId)
addQueryParameter("rc_token", token.authToken)
}.build()
request = request.newBuilder().apply {
url(url)
}.build()
}
return chain.proceed(request)
}
}
\ No newline at end of file
...@@ -3,10 +3,16 @@ package chat.rocket.android.infrastructure ...@@ -3,10 +3,16 @@ package chat.rocket.android.infrastructure
interface LocalRepository { interface LocalRepository {
companion object { companion object {
val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN" const val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN"
const val TOKEN_KEY = "token_"
const val SETTINGS_KEY = "settings_"
} }
fun save(key: String, value: String?) fun save(key: String, value: String?)
fun get(key: String): String? fun get(key: String): String?
fun clear(key: String)
fun clearAllFromServer(server: String)
} }
\ No newline at end of file
...@@ -11,4 +11,14 @@ class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : L ...@@ -11,4 +11,14 @@ class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : L
override fun get(key: String): String? { override fun get(key: String): String? {
return preferences.getString(key, null) return preferences.getString(key, null)
} }
override fun clear(key: String) {
preferences.edit().remove(key).apply()
}
override fun clearAllFromServer(server: String) {
clear(LocalRepository.KEY_PUSH_TOKEN)
clear(LocalRepository.TOKEN_KEY + server)
clear(LocalRepository.SETTINGS_KEY + server)
}
} }
\ No newline at end of file
...@@ -3,4 +3,5 @@ package chat.rocket.android.server.domain ...@@ -3,4 +3,5 @@ package chat.rocket.android.server.domain
interface CurrentServerRepository { interface CurrentServerRepository {
fun save(url: String) fun save(url: String)
fun get(): String? fun get(): String?
fun clear()
} }
\ No newline at end of file
...@@ -4,4 +4,8 @@ import javax.inject.Inject ...@@ -4,4 +4,8 @@ import javax.inject.Inject
class GetCurrentServerInteractor @Inject constructor(private val repository: CurrentServerRepository) { class GetCurrentServerInteractor @Inject constructor(private val repository: CurrentServerRepository) {
fun get(): String? = repository.get() fun get(): String? = repository.get()
fun clear() {
repository.clear()
}
} }
\ No newline at end of file
...@@ -6,4 +6,6 @@ interface MultiServerTokenRepository { ...@@ -6,4 +6,6 @@ interface MultiServerTokenRepository {
fun get(server: String): TokenModel? fun get(server: String): TokenModel?
fun save(server: String, token: TokenModel) fun save(server: String, token: TokenModel)
fun clear(server: String)
} }
\ No newline at end of file
package chat.rocket.android.server.infraestructure package chat.rocket.android.server.infraestructure
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.LocalRepository.Companion.SETTINGS_KEY
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 chat.rocket.core.model.Value import chat.rocket.core.model.Value
...@@ -21,8 +22,4 @@ class SharedPreferencesSettingsRepository(private val localRespository: LocalRep ...@@ -21,8 +22,4 @@ class SharedPreferencesSettingsRepository(private val localRespository: LocalRep
return null return null
} }
companion object {
private const val SETTINGS_KEY = "settings_"
}
} }
\ No newline at end of file
...@@ -16,4 +16,8 @@ class SharedPrefsCurrentServerRepository(private val preferences: SharedPreferen ...@@ -16,4 +16,8 @@ class SharedPrefsCurrentServerRepository(private val preferences: SharedPreferen
companion object { companion object {
private const val CURRENT_SERVER_KEY = "current_server" private const val CURRENT_SERVER_KEY = "current_server"
} }
override fun clear() {
preferences.edit().remove(CURRENT_SERVER_KEY).apply()
}
} }
\ No newline at end of file
...@@ -18,8 +18,8 @@ fun String.ifEmpty(value: String): String { ...@@ -18,8 +18,8 @@ fun String.ifEmpty(value: String): String {
return this return this
} }
fun View.setVisibility(value: Boolean) { fun View.setVisible(visible: Boolean) {
visibility = if (value) { visibility = if (visible) {
View.VISIBLE View.VISIBLE
} else { } else {
View.GONE View.GONE
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="80dp"
android:height="45dp"
android:viewportWidth="80.0"
android:viewportHeight="45.0">
<path
android:pathData="M0,0h80v45h-80z"
android:strokeColor="#00000000"
android:fillColor="#C3D1DA"
android:strokeWidth="1"/>
<path
android:pathData="M43.99,16.75C44.34,16.75 44.64,16.87 44.88,17.12C45.13,17.36 45.25,17.66 45.25,18.01L45.25,26.74C45.25,27.09 45.13,27.39 44.88,27.63C44.64,27.88 44.34,28 43.99,28L35.26,28C34.91,28 34.61,27.88 34.37,27.63C34.12,27.39 34,27.09 34,26.74L34,18.01C34,17.66 34.12,17.36 34.37,17.12C34.61,16.87 34.91,16.75 35.26,16.75L43.99,16.75ZM43.99,26.74L43.99,18.01L35.26,18.01L35.26,26.74L43.99,26.74ZM40.86,22.55L43.05,25.51L36.2,25.51L37.9,23.28L39.13,24.78L40.86,22.55Z"
android:strokeColor="#00000000"
android:fillColor="#5D8298"
android:strokeWidth="1"/>
</vector>
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="10dp" android:layout_marginBottom="6dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins" android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins" android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="10dp"> android:layout_marginTop="6dp">
<include <include
android:id="@+id/layout_avatar" android:id="@+id/layout_avatar"
...@@ -55,13 +55,13 @@ ...@@ -55,13 +55,13 @@
app:layout_constraintTop_toBottomOf="@+id/top_container" app:layout_constraintTop_toBottomOf="@+id/top_container"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" /> tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
<!-- TODO implement --> <!-- TODO - Use separate adapter items for messages and attachments. -->
<com.facebook.drawee.view.SimpleDraweeView <include
android:id="@+id/image_attachment" android:id="@+id/message_attachment"
layout="@layout/message_attachment"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="150dp" android:layout_height="wrap_content"
android:layout_marginTop="5dp" android:layout_marginTop="4dp"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@id/top_container" app:layout_constraintLeft_toLeftOf="@id/top_container"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_content" /> app:layout_constraintTop_toBottomOf="@+id/text_content" />
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:id="@+id/attachment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_attachment"
android:layout_width="match_parent"
android:layout_height="150dp"
fresco:actualImageScaleType="fitStart"
fresco:placeholderImage="@drawable/image_dummy"
tools:visibility="visible"
android:visibility="gone"/>
<FrameLayout
android:id="@+id/audio_video_attachment"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/black"
android:visibility="gone"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/exo_controls_play"/>
</FrameLayout>
<TextView
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Filename.png"/>
</LinearLayout>
\ No newline at end of file
...@@ -7,6 +7,11 @@ ...@@ -7,6 +7,11 @@
android:icon="@drawable/ic_search_white_24px" android:icon="@drawable/ic_search_white_24px"
android:title="@string/action_search" android:title="@string/action_search"
app:actionViewClass="android.support.v7.widget.SearchView" app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always|collapseActionView" /> app:showAsAction="ifRoom|collapseActionView" />
<item android:id="@+id/action_logout"
android:title="@string/action_logout"
app:showAsAction="never" />
</menu> </menu>
\ No newline at end of file
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<string name="action_privacy_policy">Política de Privacidade</string> <string name="action_privacy_policy">Política de Privacidade</string>
<string name="action_search">Pesquisar</string> <string name="action_search">Pesquisar</string>
<string name="action_update">Atualizar</string> <string name="action_update">Atualizar</string>
<string name="action_logout">Sair</string>
<!-- Regular information messages --> <!-- Regular information messages -->
<string name="msg_no_internet_connection">Sem conexão à internet</string> <string name="msg_no_internet_connection">Sem conexão à internet</string>
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
<string name="action_privacy_policy">Privacy Policy</string> <string name="action_privacy_policy">Privacy Policy</string>
<string name="action_search">Search</string> <string name="action_search">Search</string>
<string name="action_update">Update</string> <string name="action_update">Update</string>
<string name="action_logout">Log Out</string>
<!-- Regular information messages --> <!-- Regular information messages -->
<string name="msg_no_internet_connection">No internet connection</string> <string name="msg_no_internet_connection">No internet connection</string>
......
...@@ -5,6 +5,7 @@ buildscript { ...@@ -5,6 +5,7 @@ buildscript {
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url 'https://maven.fabric.io/public' }
mavenCentral() mavenCentral()
} }
...@@ -13,6 +14,7 @@ buildscript { ...@@ -13,6 +14,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.1.2' classpath 'com.google.gms:google-services:3.1.2'
classpath 'io.fabric.tools:gradle:1.+'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
......
...@@ -5,7 +5,7 @@ ext { ...@@ -5,7 +5,7 @@ ext {
targetSdk : 27, targetSdk : 27,
buildTools : '27.0.3', buildTools : '27.0.3',
kotlin : '1.2.21', kotlin : '1.2.21',
coroutine : '0.21', coroutine : '0.22',
dokka : '0.9.15', dokka : '0.9.15',
// Main dependencies // Main dependencies
...@@ -73,6 +73,7 @@ ext { ...@@ -73,6 +73,7 @@ ext {
rxBinding : "com.jakewharton.rxbinding2:rxbinding-kotlin:${versions.rxBinding}", rxBinding : "com.jakewharton.rxbinding2:rxbinding-kotlin:${versions.rxBinding}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}", fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoOkHttp : "com.facebook.fresco:imagepipeline-okhttp3:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}", frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}", frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}", frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}",
......
package chat.rocket.android.player package chat.rocket.android.player
import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View import android.view.View
import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory import com.google.android.exoplayer2.DefaultRenderersFactory
...@@ -22,10 +25,12 @@ class PlayerActivity : AppCompatActivity() { ...@@ -22,10 +25,12 @@ class PlayerActivity : AppCompatActivity() {
private var playWhenReady = true private var playWhenReady = true
private var currentWindow = 0 private var currentWindow = 0
private var playbackPosition = 0L private var playbackPosition = 0L
private lateinit var videoUrl: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player) setContentView(R.layout.activity_player)
videoUrl = intent.getStringExtra(URL_KEY)
} }
override fun onStart() { override fun onStart() {
...@@ -65,8 +70,9 @@ class PlayerActivity : AppCompatActivity() { ...@@ -65,8 +70,9 @@ class PlayerActivity : AppCompatActivity() {
player.seekTo(currentWindow, playbackPosition) player.seekTo(currentWindow, playbackPosition)
isPlayerInitialized = true isPlayerInitialized = true
} }
val uri = Uri.parse("http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4") val uri = Uri.parse(videoUrl)
val mediaSource = buildMediaSource(uri) val mediaSource = buildMediaSource(uri)
Log.d("PlayerActivity", "Player with: " + videoUrl)
player.prepare(mediaSource, true, false) player.prepare(mediaSource, true, false)
} }
...@@ -86,4 +92,13 @@ class PlayerActivity : AppCompatActivity() { ...@@ -86,4 +92,13 @@ class PlayerActivity : AppCompatActivity() {
// Read the docs for detailed explanation: https://developer.android.com/training/basics/firstapp/starting-activity.html and https://developer.android.com/design/patterns/fullscreen.html // Read the docs for detailed explanation: https://developer.android.com/training/basics/firstapp/starting-activity.html and https://developer.android.com/design/patterns/fullscreen.html
player_view.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION player_view.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
} }
companion object {
const private val URL_KEY = "URL_KEY"
fun play(context: Context, url: String) {
context.startActivity(Intent(context, PlayerActivity::class.java).apply {
putExtra(URL_KEY, 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