Skip to content
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 @@ -24,6 +24,7 @@ import com.nextcloud.talk.models.json.status.StatusOverall
import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall
import com.nextcloud.talk.models.json.threads.ThreadOverall
import com.nextcloud.talk.models.json.threads.ThreadsOverall
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEventsOverall
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import okhttp3.MultipartBody
import okhttp3.RequestBody
Expand Down Expand Up @@ -270,6 +271,12 @@ interface NcApiCoroutines {
@Url url: String
): UserAbsenceOverall

@GET
suspend fun getUpcomingEvents(
@Header("Authorization") authorization: String,
@Url url: String
): UpcomingEventsOverall

@POST
suspend fun testPushNotifications(
@Header("Authorization") authorization: String,
Expand Down
62 changes: 47 additions & 15 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Date
import java.util.Locale
import java.util.concurrent.ExecutionException
Expand Down Expand Up @@ -790,6 +791,15 @@ class ChatActivity :
}
}

conversationUser?.let { user ->
val credentials = ApiUtils.getCredentials(user.username, user.token)
chatViewModel.fetchUpcomingEvent(
credentials!!,
user.baseUrl!!,
roomToken
)
}

if (currentConversation?.objectType == ConversationEnums.ObjectType.EVENT &&
hasSpreedFeatureCapability(
conversationUser?.capabilities!!.spreedCapability!!,
Expand Down Expand Up @@ -1384,6 +1394,36 @@ class ChatActivity :
}
}

chatViewModel.upcomingEventViewState.observe(this) { uiState ->
when (uiState) {
is ChatViewModel.UpcomingEventUIState.Success -> {
binding.upcomingEventCard.visibility = View.VISIBLE
viewThemeUtils.material.themeCardView(binding.upcomingEventCard)

binding.upcomingEventContainer.upcomingEventSummary.text = uiState.event.summary

uiState.event.start?.let { start ->
val startDateTime = Instant.ofEpochSecond(start).atZone(ZoneId.systemDefault())
val currentTime = ZonedDateTime.now(ZoneId.systemDefault())
binding.upcomingEventContainer.upcomingEventTime.text =
DateUtils(context).getStringForMeetingStartDateTime(startDateTime, currentTime)
}

binding.upcomingEventContainer.upcomingEventDismiss.setOnClickListener {
binding.upcomingEventCard.visibility = View.GONE
}
}

is ChatViewModel.UpcomingEventUIState.Error -> {
Log.e(TAG, "Error fetching upcoming events", uiState.exception)
}

ChatViewModel.UpcomingEventUIState.None -> {
binding.upcomingEventCard.visibility = View.GONE
}
}
}

this.lifecycleScope.launch {
chatViewModel.threadRetrieveState.collect { uiState ->
when (uiState) {
Expand Down Expand Up @@ -2778,6 +2818,12 @@ class ChatActivity :
bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, isOneToOneConversation())

val upcomingEvent =
(chatViewModel.upcomingEventViewState.value as? ChatViewModel.UpcomingEventUIState.Success)?.event
if (upcomingEvent != null) {
bundle.putParcelable(BundleKeys.KEY_UPCOMING_EVENT, upcomingEvent)
}

val intent = Intent(this, ConversationInfoActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
Expand Down Expand Up @@ -3815,21 +3861,7 @@ class ChatActivity :

return when {
currentTime.isBefore(startDateTime) -> {
val isToday = startDateTime.toLocalDate().isEqual(currentTime.toLocalDate())
val isTomorrow = startDateTime.toLocalDate().isEqual(currentTime.toLocalDate().plusDays(1))
when {
isToday -> String.format(
context.resources.getString(R.string.nc_today_meeting),
startDateTime.format(DateTimeFormatter.ofPattern("HH:mm"))
)

isTomorrow -> String.format(
context.resources.getString(R.string.nc_tomorrow_meeting),
startDateTime.format(DateTimeFormatter.ofPattern("HH:mm"))
)

else -> startDateTime.format(DateTimeFormatter.ofPattern("MMM d, yyyy, HH:mm"))
}
DateUtils(context).getStringForMeetingStartDateTime(startDateTime, currentTime)
}

currentTime.isAfter(endDateTime) -> context.resources.getString(R.string.nc_meeting_ended)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.opengraph.Reference
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEventsOverall
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import io.reactivex.Observable
import retrofit2.Response
Expand Down Expand Up @@ -70,6 +71,7 @@ interface ChatNetworkDataSource {
fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int): Observable<GenericOverall>
suspend fun editChatMessage(credentials: String, url: String, text: String): ChatOverallSingleMessage
suspend fun getOutOfOfficeStatusForUser(credentials: String, baseUrl: String, userId: String): UserAbsenceOverall
suspend fun getUpcomingEvents(credentials: String, baseUrl: String, roomToken: String): UpcomingEventsOverall
suspend fun getContextForChatMessage(
credentials: String,
baseUrl: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.opengraph.Reference
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEventsOverall
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.message.SendMessageUtils
Expand Down Expand Up @@ -194,6 +195,16 @@ class RetrofitChatNetwork(private val ncApi: NcApi, private val ncApiCoroutines:
ApiUtils.getUrlForOutOfOffice(baseUrl, userId)
)

override suspend fun getUpcomingEvents(
credentials: String,
baseUrl: String,
roomToken: String
): UpcomingEventsOverall =
ncApiCoroutines.getUpcomingEvents(
credentials,
ApiUtils.getUrlForUpcomingEvents(baseUrl, roomToken)
)

override suspend fun getContextForChatMessage(
credentials: String,
baseUrl: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.opengraph.Reference
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.models.json.threads.ThreadInfo
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEvent
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceData
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.threadsoverview.data.ThreadsRepository
Expand Down Expand Up @@ -164,6 +165,10 @@ class ChatViewModel @Inject constructor(
val outOfOfficeViewState: LiveData<OutOfOfficeUIState>
get() = _outOfOfficeViewState

private val _upcomingEventViewState = MutableLiveData<UpcomingEventUIState>(UpcomingEventUIState.None)
val upcomingEventViewState: LiveData<UpcomingEventUIState>
get() = _upcomingEventViewState

private val _unbindRoomResult = MutableLiveData<UnbindRoomUiState>(UnbindRoomUiState.None)
val unbindRoomResult: LiveData<UnbindRoomUiState>
get() = _unbindRoomResult
Expand Down Expand Up @@ -990,6 +995,23 @@ class ChatViewModel @Inject constructor(
}
}

@Suppress("Detekt.TooGenericExceptionCaught")
fun fetchUpcomingEvent(credentials: String, baseUrl: String, roomToken: String) {
viewModelScope.launch {
try {
val response = chatNetworkDataSource.getUpcomingEvents(credentials, baseUrl, roomToken)
val firstEvent = response.ocs?.data?.events?.firstOrNull()
if (firstEvent != null) {
_upcomingEventViewState.value = UpcomingEventUIState.Success(firstEvent)
} else {
_upcomingEventViewState.value = UpcomingEventUIState.None
}
} catch (exception: Exception) {
_upcomingEventViewState.value = UpcomingEventUIState.Error(exception)
}
}
}

fun deleteTempMessage(chatMessage: ChatMessage) {
viewModelScope.launch {
chatRepository.deleteTempMessage(chatMessage)
Expand Down Expand Up @@ -1118,4 +1140,10 @@ class ChatViewModel @Inject constructor(
data class Success(val thread: ThreadInfo?) : ThreadRetrieveUiState()
data class Error(val exception: Exception) : ThreadRetrieveUiState()
}

sealed class UpcomingEventUIState {
data object None : UpcomingEventUIState()
data class Success(val event: UpcomingEvent) : UpcomingEventUIState()
data class Error(val exception: Exception) : UpcomingEventUIState()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
import com.nextcloud.talk.databinding.DialogBanParticipantBinding
import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider
import com.nextcloud.talk.extensions.getParcelableExtraProvider
import com.nextcloud.talk.extensions.loadConversationAvatar
import com.nextcloud.talk.extensions.loadNoteToSelfAvatar
import com.nextcloud.talk.extensions.loadSystemAvatar
Expand All @@ -73,6 +74,7 @@ import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelCo
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEvent
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.Participant
Expand Down Expand Up @@ -105,6 +107,7 @@ import kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.time.Instant
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
Expand Down Expand Up @@ -200,6 +203,19 @@ class ConversationInfoActivity :

hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)

val upcomingEvent = intent.getParcelableExtraProvider<UpcomingEvent>(BundleKeys.KEY_UPCOMING_EVENT)
if (upcomingEvent != null && (upcomingEvent.summary != null || upcomingEvent.start != null)) {
binding.upcomingEventCard.visibility = VISIBLE
viewThemeUtils.material.themeCardView(binding.upcomingEventCard)
binding.upcomingEventContainer.upcomingEventSummary.text = upcomingEvent.summary
upcomingEvent.start?.let { start ->
val startDateTime = Instant.ofEpochSecond(start).atZone(ZoneId.systemDefault())
val currentTime = ZonedDateTime.now(ZoneId.systemDefault())
binding.upcomingEventContainer.upcomingEventTime.text =
DateUtils(this).getStringForMeetingStartDateTime(startDateTime, currentTime)
}
}

viewModel = ViewModelProvider(this, viewModelFactory)[ConversationInfoViewModel::class.java]

lifecycleScope.launch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.models.json.upcomingEvents

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

@Parcelize
@JsonObject
data class UpcomingEvent(
@JsonField(name = ["uri"])
var uri: String,
@JsonField(name = ["recurrenceId"])
var recurrenceId: Long?,
@JsonField(name = ["calendarUri"])
var calendarUri: String,
@JsonField(name = ["start"])
var start: Long?,
@JsonField(name = ["summary"])
var summary: String?,
@JsonField(name = ["location"])
var location: String?,
@JsonField(name = ["calendarAppUrl"])
var calendarAppUrl: String?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this("", null, "", null, null, null, null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.models.json.upcomingEvents

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

@Parcelize
@JsonObject
data class UpcomingEventsData(
@JsonField(name = ["events"])
var events: List<UpcomingEvent>?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.models.json.upcomingEvents

import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import com.nextcloud.talk.models.json.generic.GenericMeta
import kotlinx.parcelize.Parcelize

@Parcelize
@JsonObject
data class UpcomingEventsOCS(
@JsonField(name = ["meta"])
var meta: GenericMeta?,
@JsonField(name = ["data"])
var data: UpcomingEventsData?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.models.json.upcomingEvents

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

@Parcelize
@JsonObject
data class UpcomingEventsOverall(
@JsonField(name = ["ocs"])
var ocs: UpcomingEventsOCS?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}
5 changes: 5 additions & 0 deletions app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.nextcloud.talk.models.RetrofitBucket
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import okhttp3.Credentials.basic
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

@Suppress("TooManyFunctions")
Expand Down Expand Up @@ -533,6 +534,10 @@ object ApiUtils {
fun getUrlForOutOfOffice(baseUrl: String, userId: String): String =
"$baseUrl$OCS_API_VERSION/apps/dav/api/v1/outOfOffice/$userId/now"

fun getUrlForUpcomingEvents(baseUrl: String, roomToken: String): String =
"$baseUrl$OCS_API_VERSION/apps/dav/api/v1/events/upcoming" +
"?location=${URLEncoder.encode("$baseUrl/call/$roomToken", "UTF-8")}"

fun getUrlForChatMessageContext(baseUrl: String, token: String, messageId: String): String =
"$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/chat/$token/$messageId/context"

Expand Down
Loading
Loading