Skip to content

Commit

Permalink
Merge pull request #4521 from nextcloud/out_of_office
Browse files Browse the repository at this point in the history
Out of office
  • Loading branch information
sowjanyakch authored Dec 20, 2024
2 parents fcf68ce + cd50115 commit 5a7c45e
Show file tree
Hide file tree
Showing 14 changed files with 426 additions and 6 deletions.
7 changes: 7 additions & 0 deletions app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.AddParticipantOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.participants.TalkBanOverall
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
Expand Down Expand Up @@ -197,4 +198,10 @@ interface NcApiCoroutines {
@Url url: String,
@Field("seconds") seconds: Int
): GenericOverall

@GET
suspend fun getOutOfOfficeStatusForUser(
@Header("Authorization") authorization: String,
@Url url: String
): UserAbsenceOverall
}
139 changes: 136 additions & 3 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,18 @@ import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.AbsListView
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.view.ContextThemeWrapper
import androidx.cardview.widget.CardView
import androidx.core.content.FileProvider
import androidx.core.content.PermissionChecker
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
import androidx.core.graphics.ColorUtils
import androidx.core.graphics.drawable.toBitmap
import androidx.core.text.bold
import androidx.emoji2.text.EmojiCompat
Expand All @@ -70,11 +73,13 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager
import autodagger.AutoInjector
import coil.imageLoader
import coil.load
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.color.ColorUtil
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
Expand Down Expand Up @@ -209,7 +214,6 @@ import java.util.Date
import java.util.Locale
import java.util.concurrent.ExecutionException
import javax.inject.Inject
import kotlin.String
import kotlin.collections.set
import kotlin.math.roundToInt

Expand Down Expand Up @@ -240,6 +244,9 @@ class ChatActivity :
@Inject
lateinit var dateUtils: DateUtils

@Inject
lateinit var colorUtil: ColorUtil

@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

Expand Down Expand Up @@ -568,7 +575,7 @@ class ChatActivity :
this.lifecycle.removeObserver(chatViewModel)
}

@SuppressLint("NotifyDataSetChanged")
@SuppressLint("NotifyDataSetChanged", "SetTextI18n", "ResourceAsColor")
@Suppress("LongMethod")
private fun initObservers() {
Log.d(TAG, "initObservers Called")
Expand Down Expand Up @@ -684,9 +691,21 @@ class ChatActivity :
loadAvatarForStatusBar()
setupSwipeToReply()
setActionBarTitle()

checkShowCallButtons()
checkLobbyState()
if (currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
currentConversation?.status == "dnd"
) {
conversationUser?.let { user ->
val credentials = ApiUtils.getCredentials(user.username, user.token)
chatViewModel.outOfOfficeStatusOfUser(
credentials!!,
user.baseUrl!!,
currentConversation!!.name
)
}
}

updateRoomTimerHandler()

val urlForChatting =
Expand Down Expand Up @@ -1053,6 +1072,99 @@ class ChatActivity :
chatViewModel.recordTouchObserver.observe(this) { y ->
binding.voiceRecordingLock.y -= y
}

chatViewModel.outOfOfficeViewState.observe(this) { uiState ->
when (uiState) {
is ChatViewModel.OutOfOfficeUIState.Error -> {
Log.e(TAG, "Error fetching/ no user absence data", uiState.exception)
}
ChatViewModel.OutOfOfficeUIState.None -> {
}
is ChatViewModel.OutOfOfficeUIState.Success -> {
binding.outOfOfficeContainer.visibility = View.VISIBLE

val backgroundColor = colorUtil.getNullSafeColorWithFallbackRes(
conversationUser!!.capabilities!!.themingCapability!!.color,
R.color.colorPrimary
)

binding.outOfOfficeContainer.findViewById<View>(
R.id.verticalLine
).setBackgroundColor(backgroundColor)
val setAlpha = ColorUtils.setAlphaComponent(backgroundColor, OUT_OF_OFFICE_ALPHA)
binding.outOfOfficeContainer.setCardBackgroundColor(setAlpha)

val startDateTimestamp: Long = uiState.userAbsence.startDate.toLong()
val endDateTimestamp: Long = uiState.userAbsence.endDate.toLong()

val startDate = Date(startDateTimestamp * ONE_SECOND_IN_MILLIS)
val endDate = Date(endDateTimestamp * ONE_SECOND_IN_MILLIS)

if (dateUtils.isSameDate(startDate, endDate)) {
binding.outOfOfficeContainer.findViewById<TextView>(R.id.userAbsenceShortMessage).text =
String.format(
context.resources.getString(R.string.user_absence_for_one_day),
uiState.userAbsence.userId
)
binding.outOfOfficeContainer.findViewById<TextView>(R.id.userAbsencePeriod).visibility =
View.GONE
} else {
val dateFormatter = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
val startDateString = dateFormatter.format(startDate)
val endDateString = dateFormatter.format(endDate)
binding.outOfOfficeContainer.findViewById<TextView>(R.id.userAbsenceShortMessage).text =
String.format(
context.resources.getString(R.string.user_absence),
uiState.userAbsence.userId
)

binding.outOfOfficeContainer.findViewById<TextView>(R.id.userAbsencePeriod).text =
"$startDateString - $endDateString"
}

if (uiState.userAbsence.replacementUserDisplayName != null) {
var imageUri = Uri.parse(
ApiUtils.getUrlForAvatar(
conversationUser?.baseUrl,
uiState.userAbsence
.replacementUserId,
false
)
)
if (DisplayUtils.isDarkModeOn(context)) {
imageUri = Uri.parse(
ApiUtils.getUrlForAvatarDarkTheme(
conversationUser?.baseUrl,
uiState
.userAbsence
.replacementUserId,
false
)
)
}
binding.outOfOfficeContainer.findViewById<TextView>(R.id.absenceReplacement).text =
context.resources.getString(R.string.user_absence_replacement)
binding.outOfOfficeContainer.findViewById<ImageView>(R.id.replacement_user_avatar)
.load(imageUri) {
transformations(CircleCropTransformation())
placeholder(R.drawable.account_circle_96dp)
error(R.drawable.account_circle_96dp)
crossfade(true)
}
binding.outOfOfficeContainer.findViewById<TextView>(R.id.replacement_user_name).text =
uiState.userAbsence.replacementUserDisplayName
} else {
binding.outOfOfficeContainer.findViewById<LinearLayout>(R.id.userAbsenceReplacement)
.visibility = View.GONE
}
binding.outOfOfficeContainer.findViewById<TextView>(R.id.userAbsenceLongMessage).text =
uiState.userAbsence.message
binding.outOfOfficeContainer.findViewById<CardView>(R.id.avatar_chip).setOnClickListener {
joinOneToOneConversation(uiState.userAbsence.replacementUserId!!)
}
}
}
}
}

private fun removeUnreadMessagesMarker() {
Expand Down Expand Up @@ -3819,6 +3931,24 @@ class ChatActivity :
startActivity(shareIntent)
}

fun joinOneToOneConversation(userId: String) {
val apiVersion =
ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1))
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
conversationUser?.baseUrl!!,
ROOM_TYPE_ONE_TO_ONE,
ACTOR_TYPE,
userId,
null
)
chatViewModel.createRoom(
credentials!!,
retrofitBucket.url!!,
retrofitBucket.queryMap!!
)
}

companion object {
val TAG = ChatActivity::class.simpleName
private const val CONTENT_TYPE_CALL_STARTED: Byte = 1
Expand Down Expand Up @@ -3871,7 +4001,10 @@ class ChatActivity :
private const val FIVE_MINUTES_IN_SECONDS: Long = 300
private const val TEMPORARY_MESSAGE_ID_INT: Int = -3
private const val TEMPORARY_MESSAGE_ID_STRING: String = "-3"
private const val ROOM_TYPE_ONE_TO_ONE = "1"
private const val ACTOR_TYPE = "users"
const val CONVERSATION_INTERNAL_ID = "CONVERSATION_INTERNAL_ID"
const val NO_OFFLINE_MESSAGES_FOUND = "NO_OFFLINE_MESSAGES_FOUND"
const val OUT_OF_OFFICE_ALPHA = 76
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import io.reactivex.Observable
import retrofit2.Response

Expand Down Expand Up @@ -63,4 +64,5 @@ interface ChatNetworkDataSource {
fun createRoom(credentials: String, url: String, map: Map<String, String>): Observable<RoomOverall>
fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int): Observable<GenericOverall>
fun editChatMessage(credentials: String, url: String, text: String): Observable<ChatOverallSingleMessage>
suspend fun getOutOfOfficeStatusForUser(credentials: String, baseUrl: String, userId: String): UserAbsenceOverall
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package com.nextcloud.talk.chat.data.network

import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
Expand All @@ -15,11 +16,15 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observable
import retrofit2.Response

class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
class RetrofitChatNetwork(
private val ncApi: NcApi,
private val ncApiCoroutines: NcApiCoroutines
) : ChatNetworkDataSource {
override fun getRoom(user: User, roomToken: String): Observable<ConversationModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
Expand Down Expand Up @@ -178,4 +183,15 @@ class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
override fun editChatMessage(credentials: String, url: String, text: String): Observable<ChatOverallSingleMessage> {
return ncApi.editChatMessage(credentials, url, text).map { it }
}

override suspend fun getOutOfOfficeStatusForUser(
credentials: String,
baseUrl: String,
userId: String
): UserAbsenceOverall {
return ncApiCoroutines.getOutOfOfficeStatusForUser(
credentials,
ApiUtils.getUrlForOutOfOffice(baseUrl, userId)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
Expand All @@ -33,6 +34,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceData
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.PlaybackSpeed
import com.nextcloud.talk.utils.ConversationUtils
Expand All @@ -47,6 +49,7 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import java.io.File
import javax.inject.Inject

Expand Down Expand Up @@ -109,6 +112,10 @@ class ChatViewModel @Inject constructor(
val getVoiceRecordingLocked: LiveData<Boolean>
get() = _getVoiceRecordingLocked

private val _outOfOfficeViewState = MutableLiveData<OutOfOfficeUIState>(OutOfOfficeUIState.None)
val outOfOfficeViewState: LiveData<OutOfOfficeUIState>
get() = _outOfOfficeViewState

private val _voiceMessagePlaybackSpeedPreferences: MutableLiveData<Map<String, PlaybackSpeed>> = MutableLiveData()
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
get() = _voiceMessagePlaybackSpeedPreferences
Expand Down Expand Up @@ -764,8 +771,26 @@ class ChatViewModel @Inject constructor(
}
}

@Suppress("Detekt.TooGenericExceptionCaught")
fun outOfOfficeStatusOfUser(credentials: String, baseUrl: String, userId: String) {
viewModelScope.launch {
try {
val response = chatNetworkDataSource.getOutOfOfficeStatusForUser(credentials, baseUrl, userId)
_outOfOfficeViewState.value = OutOfOfficeUIState.Success(response.ocs?.data!!)
} catch (exception: Exception) {
_outOfOfficeViewState.value = OutOfOfficeUIState.Error(exception)
}
}
}

companion object {
private val TAG = ChatViewModel::class.simpleName
const val JOIN_ROOM_RETRY_COUNT: Long = 3
}

sealed class OutOfOfficeUIState {
data object None : OutOfOfficeUIState()
data class Success(val userAbsence: UserAbsenceData) : OutOfOfficeUIState()
data class Error(val exception: Exception) : OutOfOfficeUIState()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ class RepositoryModule {
}

@Provides
fun provideChatNetworkDataSource(ncApi: NcApi): ChatNetworkDataSource {
return RetrofitChatNetwork(ncApi)
fun provideChatNetworkDataSource(ncApi: NcApi, ncApiCoroutines: NcApiCoroutines): ChatNetworkDataSource {
return RetrofitChatNetwork(ncApi, ncApiCoroutines)
}

@Provides
Expand Down
Loading

0 comments on commit 5a7c45e

Please sign in to comment.