Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Out of office #4521

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
135 changes: 132 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 @@ -51,9 +51,11 @@ 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 +72,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 +213,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 +243,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 +574,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 +690,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 +1071,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 in outOfOfficeState", 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, (0.3f * 255).toInt())
binding.outOfOfficeContainer.setCardBackgroundColor(setAlpha)

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

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

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<TextView>(R.id.absenceReplacement).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 @@ -3808,6 +3919,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!!,
"1",
"users",
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
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,25 @@ class ChatViewModel @Inject constructor(
}
}

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.models.json.userAbsence

import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize

@Parcelize
@JsonObject
data class UserAbsenceData(
@JsonField(name = ["id"])
var id: String,
@JsonField(name = ["userId"])
var userId: String,
@JsonField(name = ["startDate"])
var startDate: Int,
@JsonField(name = ["endDate"])
var endDate: Int,
@JsonField(name = ["shortMessage"])
var shortMessage: String,
@JsonField(name = ["message"])
var message: String,
@JsonField(name = ["replacementUserId"])
var replacementUserId: String?,
@JsonField(name = ["replacementUserDisplayName"])
var replacementUserDisplayName: String?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() :
this("", "", 0, 0, "", "", null, null)
}
Loading
Loading