Unverified Commit a42cba32 authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #724 from RocketChat/feature-2.x/attachments

[NEW] Show attachments
parents f09e71c4 c9de653c
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
......@@ -11,8 +12,8 @@ android {
applicationId "chat.rocket.android"
minSdkVersion 21
targetSdkVersion versions.targetSdk
versionCode 1001
versionName "2.0.0-dev1"
versionCode 1007
versionName "2.0.0-dev5.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
......@@ -63,6 +64,7 @@ dependencies {
implementation libraries.daggerSupport
kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt
implementation libraries.playServicesGcm
implementation libraries.room
......@@ -80,6 +82,7 @@ dependencies {
implementation libraries.threeTenABP
implementation libraries.fresco
api libraries.frescoOkHttp
implementation libraries.frescoAnimatedGif
implementation libraries.frescoWebP
implementation libraries.frescoAnimatedWebP
......@@ -97,6 +100,10 @@ dependencies {
implementation libraries.textDrawable
implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true
}
testImplementation libraries.junit
androidTestImplementation(libraries.expressoCore, {
exclude group: 'com.android.support', module: 'support-annotations'
......@@ -116,4 +123,4 @@ task compileSdk(type:Exec) {
}
preBuild.dependsOn compileSdk
apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
apply plugin: 'com.google.gms.google-services'
......@@ -75,6 +75,11 @@
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
<meta-data
android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
</application>
</manifest>
\ No newline at end of file
......@@ -4,8 +4,15 @@ import android.app.Activity
import android.app.Application
import android.app.Service
import chat.rocket.android.BuildConfig
import chat.rocket.android.app.utils.CustomImageFormatConfigurator
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.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
......@@ -14,9 +21,12 @@ import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector
import dagger.android.HasServiceInjector
import io.fabric.sdk.android.Fabric
import timber.log.Timber
import javax.inject.Inject
class RocketChatApplication : Application(), HasActivityInjector, HasServiceInjector {
@Inject
......@@ -25,37 +35,60 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject
lateinit var serviceDispatchingAndroidInjector: DispatchingAndroidInjector<Service>
companion object {
lateinit var instance: RocketChatApplication
}
@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() {
super.onCreate()
DaggerAppComponent.builder().application(this).build().inject(this)
// TODO - remove this when we have a proper service handling connection...
initCurrentServer()
AndroidThreeTen.init(this)
setupCrashlytics()
setupFresco()
setupTimber()
instance = this
}
private fun setupFresco() {
val imagePipelineConfig = ImagePipelineConfig.newBuilder(this)
.setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
.build()
val draweeConfigBuilder = DraweeConfig.newBuilder()
// TODO - remove this when we have a proper service handling connection...
private fun initCurrentServer() {
val currentServer = getCurrentServerInteractor.get()
val serverToken = currentServer?.let { multiServerRepository.get(currentServer) }
val settings = currentServer?.let { settingsRepository.get(currentServer) }
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() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
} else {
Timber.plant(CrashlyticsTree())
}
}
......
package chat.rocket.android.authentication.di
import android.content.Context
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.authentication.ui.AuthenticationActivity
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.Provides
......@@ -17,10 +13,4 @@ class AuthenticationModule {
@Provides
@PerActivity
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
......@@ -17,7 +17,7 @@ import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
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 dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
......@@ -92,8 +92,8 @@ class LoginFragment : Fragment(), LoginView {
override fun showOauthView(value: Boolean) {
if (value) {
social_accounts_container.setVisibility(true)
button_fab.setVisibility(true)
social_accounts_container.setVisible(true)
button_fab.setVisible(true)
// 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).
......@@ -102,8 +102,8 @@ class LoginFragment : Fragment(), LoginView {
isGlobalLayoutListenerSetUp = true
}
} else {
social_accounts_container.setVisibility(false)
button_fab.setVisibility(false)
social_accounts_container.setVisible(false)
button_fab.setVisible(false)
}
}
......@@ -143,7 +143,7 @@ class LoginFragment : Fragment(), LoginView {
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() {
AnimationHelper.vibrateSmartPhone(appContext)
......@@ -159,11 +159,11 @@ class LoginFragment : Fragment(), LoginView {
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
view_loading.setVisible(true)
}
override fun hideLoading() {
view_loading.setVisibility(false)
view_loading.setVisible(false)
enableUserInput(true)
}
......@@ -189,7 +189,7 @@ class LoginFragment : Fragment(), LoginView {
}
private fun showLoginButton(value: Boolean) {
button_log_in.setVisibility(value)
button_log_in.setVisible(value)
}
private fun setupSignUpListener() {
......
......@@ -48,11 +48,11 @@ class ServerFragment : Fragment(), ServerView {
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
view_loading.setVisible(true)
}
override fun hideLoading() {
view_loading.setVisibility(false)
view_loading.setVisible(false)
enableUserInput(true)
}
......
......@@ -14,7 +14,7 @@ import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.AnimationHelper
import chat.rocket.android.helper.KeyboardHelper
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 dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
......@@ -26,9 +26,9 @@ class SignupFragment : Fragment(), SignupView {
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(constraint_layout.rootView)) {
text_new_user_agreement.setVisibility(false)
text_new_user_agreement.setVisible(false)
} else {
text_new_user_agreement.setVisibility(true)
text_new_user_agreement.setVisible(true)
}
}
......@@ -92,11 +92,11 @@ class SignupFragment : Fragment(), SignupView {
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
view_loading.setVisible(true)
}
override fun hideLoading() {
view_loading.setVisibility(false)
view_loading.setVisible(false)
enableUserInput(true)
}
......
......@@ -14,7 +14,7 @@ import chat.rocket.android.R
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
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 dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
......@@ -75,11 +75,11 @@ class TwoFAFragment : Fragment(), TwoFAView {
override fun showLoading() {
enableUserInput(false)
view_loading.setVisibility(true)
view_loading.setVisible(true)
}
override fun hideLoading() {
view_loading.setVisibility(false)
view_loading.setVisible(false)
enableUserInput(true)
}
......
......@@ -6,8 +6,10 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
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.core.internal.realtime.State
import chat.rocket.core.internal.realtime.connect
import chat.rocket.core.internal.realtime.subscribeRoomMessages
import chat.rocket.core.internal.realtime.unsubscibre
import chat.rocket.core.internal.rest.messages
......@@ -15,6 +17,7 @@ import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message
import chat.rocket.core.model.Value
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject
......@@ -23,7 +26,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val strategy: CancelStrategy,
getSettingsInteractor: GetSettingsInteractor,
private val serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory) {
factory: RocketChatClientFactory,
private val mapper: MessageViewModelMapper) {
private val client = factory.create(serverInteractor.get()!!)
private val roomMessages = ArrayList<Message>()
private var subId: String? = null
......@@ -33,16 +37,24 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
settings = getSettingsInteractor.get(serverInteractor.get()!!)
}
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) {
view.showLoading()
try {
val messages = client.messages(chatRoomId, BaseRoom.RoomType.valueOf(chatRoomType), offset.toLong(), 30).result
val messages = client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
synchronized(roomMessages) {
roomMessages.addAll(messages)
}
val messagesViewModels = MessageViewModelMapper.mapToViewModelList(messages, settings)
val messagesViewModels = mapper.mapToViewModelList(messages, settings)
view.showMessages(messagesViewModels, serverInteractor.get()!!)
// Subscribe after getting the first page of messages from REST
if (offset == 0L) {
subscribeMessages(chatRoomId)
}
} catch (ex: Exception) {
ex.printStackTrace()
ex.message?.let {
......@@ -77,16 +89,50 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
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) {
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) {
Timber.d("subscribe messages for $roomId: $it")
}
listenMessages(roomId)
}
}*/
}
fun unsubscribeMessages() {
launch(CommonPool) {
client.removeStateChannel(stateChannel)
subId?.let { subscriptionId ->
client.unsubscibre(subscriptionId)
}
......@@ -107,7 +153,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private fun updateMessage(streamedMessage: Message) {
launchUI(strategy) {
val viewModelStreamedMessage = MessageViewModelMapper.mapToViewModel(streamedMessage, settings)
val viewModelStreamedMessage = mapper.mapToViewModel(streamedMessage, settings)
synchronized(roomMessages) {
val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id }
if (index != -1) {
......
......@@ -4,14 +4,19 @@ import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import chat.rocket.android.R
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.setVisibility
import chat.rocket.android.util.setVisible
import chat.rocket.common.util.ifNull
import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_message.view.*
import kotlinx.android.synthetic.main.message_attachment.view.*
class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() {
......@@ -61,12 +66,61 @@ class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<Chat
text_user_name.text = message.sender
text_message_time.text = message.time
text_content.text = message.content
bindAttachment(message, message_attachment, image_attachment, audio_video_attachment,
file_name)
}
private fun bindAttachment(message: MessageViewModel,
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 bindUserAvatar(message: MessageViewModel, drawee: SimpleDraweeView, imageUnknownAvatar: ImageView) = message.getAvatarUrl(serverUrl).let {
drawee.setImageURI(it.toString())
drawee.setVisible(true)
imageUnknownAvatar.setVisible(false)
}.ifNull {
imageUnknownAvatar.setVisibility(true)
drawee.setVisible(false)
imageUnknownAvatar.setVisible(true)
}
}
}
\ No newline at end of file
}
......@@ -15,7 +15,7 @@ import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
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 dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_room.*
......@@ -67,7 +67,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.loadMessages(chatRoomId, chatRoomType)
presenter.subscribeMessages(chatRoomId)
setupComposer()
}
......@@ -87,7 +86,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
if (dataSet.size >= 30) {
recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
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 {
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()
......@@ -134,8 +133,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
private fun setupComposer() {
if (isChatRoomReadOnly) {
text_room_is_read_only.setVisibility(true)
top_container.setVisibility(false)
text_room_is_read_only.setVisible(true)
top_container.setVisible(false)
} else {
text_send.setOnClickListener { sendMessage(text_message.textContent) }
}
......
......@@ -8,24 +8,52 @@ import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import chat.rocket.android.R
import chat.rocket.android.app.RocketChatApplication
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(private val message: Message,
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(RocketChatApplication.instance)
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? {
......@@ -41,21 +69,21 @@ data class MessageViewModel(private val message: Message,
val username = message.sender?.username
val realName = message.sender?.name
val senderName = if (useRealName) realName else username
return if (senderName == null) username.toString() else senderName.toString()
return senderName ?: username.toString()
}
fun getContent(context: Context): CharSequence {
val contentMessage: CharSequence
when (message.type) {
//TODO: Add implementation for Welcome type.
MESSAGE_REMOVED -> contentMessage = getSystemMessage(context.getString(R.string.message_removed))
USER_JOINED -> contentMessage = getSystemMessage(context.getString(R.string.message_user_joined_channel))
USER_LEFT -> contentMessage = getSystemMessage(context.getString(R.string.message_user_left))
USER_ADDED -> contentMessage = getSystemMessage(
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))
ROOM_NAME_CHANGED -> contentMessage = getSystemMessage(
is RoomNameChanged -> contentMessage = getSystemMessage(
context.getString(R.string.message_room_name_changed, message.message, message.sender?.username))
USER_REMOVED -> contentMessage = getSystemMessage(
is UserRemoved -> contentMessage = getSystemMessage(
context.getString(R.string.message_user_removed_by, message.message, message.sender?.username))
else -> contentMessage = getNormalMessage()
}
......@@ -91,4 +119,23 @@ data class MessageViewModel(private val message: Message,
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
object MessageViewModelMapper {
class MessageViewModelMapper @Inject constructor(private val context: Context, private val tokenRepository: TokenRepository) {
suspend fun mapToViewModel(message: Message, settings: Map<String, Value<Any>>?) = MessageViewModel(message, settings)
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> {
val vmList = mutableListOf<MessageViewModel>()
for (msg in messageList) {
vmList.add(MessageViewModel(msg, settings))
}
return vmList
return messageList.map { MessageViewModel(context, tokenRepository.get(), it, settings) }
}
}
\ No newline at end of file
......@@ -13,6 +13,7 @@ import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.Channel
import timber.log.Timber
import javax.inject.Inject
......@@ -27,6 +28,8 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val currentServer = serverInteractor.get()!!
private var reloadJob: Deferred<List<ChatRoom>>? = null
private val stateChannel = Channel<State>()
fun loadChatRooms() {
launchUI(strategy) {
view.showLoading()
......@@ -36,7 +39,8 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
fun loadChatRoom(chatRoom: ChatRoom) = navigator.toChatRoom(chatRoom.id, chatRoom.name, chatRoom.type.name, chatRoom.readonly ?: false)
fun loadChatRoom(chatRoom: ChatRoom) = navigator.toChatRoom(chatRoom.id, chatRoom.name,
chatRoom.type.toString(), chatRoom.readonly ?: false)
/**
* Gets a [ChatRoom] list from local repository.
......@@ -80,8 +84,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
// TODO - Temporary stuff, remove when adding DB support
private suspend fun subscribeRoomUpdates() {
client.addStateChannel(stateChannel)
launch(CommonPool + strategy.jobs) {
for (status in client.statusChannel) {
for (status in stateChannel) {
Timber.d("Changing status to: $status")
when (status) {
State.Authenticating -> Timber.d("Authenticating")
......@@ -243,6 +248,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
fun disconnect() {
client.removeStateChannel(stateChannel)
client.disconnect()
}
}
\ No newline at end of file
......@@ -11,9 +11,9 @@ import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.helper.UrlHelper
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.model.BaseRoom.RoomType
import chat.rocket.common.model.RoomType
import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.avatar.view.*
......@@ -50,14 +50,14 @@ class ChatRoomsAdapter(private val context: Context,
private fun bindAvatar(chatRoom: ChatRoom, avatarLayout: View, drawee: SimpleDraweeView, imageView: ImageView) {
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))
imageView.setVisibility(false)
avatarLayout.setVisibility(true)
imageView.setVisible(false)
avatarLayout.setVisible(true)
} else {
imageView.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName))
avatarLayout.setVisibility(false)
imageView.setVisibility(true)
avatarLayout.setVisible(false)
imageView.setVisible(true)
}
}
......@@ -99,11 +99,11 @@ class ChatRoomsAdapter(private val context: Context,
when {
totalUnreadMessage in 1..99 -> {
textView.textContent = totalUnreadMessage.toString()
textView.setVisibility(true)
textView.setVisible(true)
}
totalUnreadMessage > 99 -> {
textView.textContent = context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
textView.setVisibility(true)
textView.setVisible(true)
}
}
}
......
......@@ -12,7 +12,7 @@ import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.setVisible
import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection
......@@ -104,9 +104,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
}
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()
......
......@@ -6,11 +6,16 @@ import android.content.Context
import android.content.SharedPreferences
import chat.rocket.android.BuildConfig
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.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.SharedPrefsLocalRepository
import chat.rocket.android.server.domain.ChatRoomsRepository
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.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.ServerDao
......@@ -21,12 +26,19 @@ import chat.rocket.android.util.TimberLogger
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
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 dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import javax.inject.Singleton
@Module
......@@ -71,11 +83,14 @@ class AppModule {
@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor()
val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message ->
Timber.d(message)
})
if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
interceptor.level = HttpLoggingInterceptor.Level.HEADERS
// TODO - change to HEADERS on production...
interceptor.level = HttpLoggingInterceptor.Level.BODY
}
return interceptor
......@@ -89,6 +104,46 @@ class AppModule {
}.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
@Singleton
fun provideTokenRepository(): TokenRepository {
......@@ -135,4 +190,10 @@ class AppModule {
fun provideMoshi(): Moshi {
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
......@@ -13,8 +13,8 @@ fun String.ifEmpty(value: String): String {
return this
}
fun View.setVisibility(value: Boolean) {
visibility = if (value) {
fun View.setVisible(visible: Boolean) {
visibility = if (visible) {
View.VISIBLE
} else {
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 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
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_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="10dp">
android:layout_marginTop="6dp">
<include
android:id="@+id/layout_avatar"
......@@ -55,13 +55,13 @@
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!" />
<!-- TODO implement -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_attachment"
<!-- TODO - Use separate adapter items for messages and attachments. -->
<include
android:id="@+id/message_attachment"
layout="@layout/message_attachment"
android:layout_width="0dp"
android:layout_height="150dp"
android:layout_marginTop="5dp"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layout_constraintLeft_toLeftOf="@id/top_container"
app:layout_constraintRight_toRightOf="parent"
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
......@@ -5,6 +5,7 @@ buildscript {
repositories {
google()
jcenter()
maven { url 'https://maven.fabric.io/public' }
mavenCentral()
}
......@@ -13,6 +14,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
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
// in the individual module build.gradle files
......
......@@ -4,8 +4,8 @@ ext {
compileSdk : 27,
targetSdk : 27,
buildTools : '27.0.3',
kotlin : '1.2.10',
coroutine : '0.21',
kotlin : '1.2.21',
coroutine : '0.22',
dokka : '0.9.15',
// Main dependencies
......@@ -71,6 +71,7 @@ ext {
threeTenABP : "com.jakewharton.threetenabp:threetenabp:${versions.threeTenABP}",
fresco : "com.facebook.fresco:fresco:${versions.fresco}",
frescoOkHttp : "com.facebook.fresco:imagepipeline-okhttp3:${versions.fresco}",
frescoAnimatedGif : "com.facebook.fresco:animated-gif:${versions.fresco}",
frescoWebP : "com.facebook.fresco:webpsupport:${versions.fresco}",
frescoAnimatedWebP : "com.facebook.fresco:animated-webp:${versions.fresco}",
......
package chat.rocket.android.player
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory
......@@ -22,10 +25,12 @@ class PlayerActivity : AppCompatActivity() {
private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L
private lateinit var videoUrl: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
videoUrl = intent.getStringExtra(URL_KEY)
}
override fun onStart() {
......@@ -65,8 +70,9 @@ class PlayerActivity : AppCompatActivity() {
player.seekTo(currentWindow, playbackPosition)
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)
Log.d("PlayerActivity", "Player with: " + videoUrl)
player.prepare(mediaSource, true, false)
}
......@@ -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
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