Skip to content

Commit 938b725

Browse files
authored
Merge pull request #5865 from nextcloud/feat/5661/nextMeeting
📆 Show "Next meeting" in the chat
2 parents 7a5da28 + b42b7a7 commit 938b725

28 files changed

Lines changed: 1315 additions & 22 deletions

app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/24.json

Lines changed: 806 additions & 0 deletions
Large diffs are not rendered by default.

app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.nextcloud.talk.models.json.status.StatusOverall
2424
import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall
2525
import com.nextcloud.talk.models.json.threads.ThreadOverall
2626
import com.nextcloud.talk.models.json.threads.ThreadsOverall
27+
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEventsOverall
2728
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
2829
import okhttp3.MultipartBody
2930
import okhttp3.RequestBody
@@ -270,6 +271,12 @@ interface NcApiCoroutines {
270271
@Url url: String
271272
): UserAbsenceOverall
272273

274+
@GET
275+
suspend fun getUpcomingEvents(
276+
@Header("Authorization") authorization: String,
277+
@Url url: String
278+
): UpcomingEventsOverall
279+
273280
@POST
274281
suspend fun testPushNotifications(
275282
@Header("Authorization") authorization: String,

app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ import java.text.SimpleDateFormat
250250
import java.time.Instant
251251
import java.time.ZoneId
252252
import java.time.ZonedDateTime
253-
import java.time.format.DateTimeFormatter
254253
import java.util.Date
255254
import java.util.Locale
256255
import java.util.concurrent.ExecutionException
@@ -790,6 +789,15 @@ class ChatActivity :
790789
}
791790
}
792791

792+
conversationUser?.let { user ->
793+
val credentials = ApiUtils.getCredentials(user.username, user.token)
794+
chatViewModel.fetchUpcomingEvent(
795+
credentials!!,
796+
user.baseUrl!!,
797+
roomToken
798+
)
799+
}
800+
793801
if (currentConversation?.objectType == ConversationEnums.ObjectType.EVENT &&
794802
hasSpreedFeatureCapability(
795803
conversationUser?.capabilities!!.spreedCapability!!,
@@ -1384,6 +1392,47 @@ class ChatActivity :
13841392
}
13851393
}
13861394

1395+
chatViewModel.upcomingEventViewState.observe(this) { uiState ->
1396+
when (uiState) {
1397+
is ChatViewModel.UpcomingEventUIState.Success -> {
1398+
val hiddenEventKey = "${uiState.event.uri}${uiState.event.start}${uiState.event.summary}"
1399+
if (hiddenEventKey == chatViewModel.hiddenUpcomingEvent) {
1400+
binding.upcomingEventCard.visibility = View.GONE
1401+
} else {
1402+
binding.upcomingEventCard.visibility = View.VISIBLE
1403+
viewThemeUtils.material.themeCardView(binding.upcomingEventCard)
1404+
1405+
binding.upcomingEventContainer.upcomingEventSummary.text = uiState.event.summary
1406+
1407+
uiState.event.start?.let { start ->
1408+
val startDateTime = Instant.ofEpochSecond(start).atZone(ZoneId.systemDefault())
1409+
val currentTime = ZonedDateTime.now(ZoneId.systemDefault())
1410+
binding.upcomingEventContainer.upcomingEventTime.text =
1411+
DateUtils(context).getStringForMeetingStartDateTime(startDateTime, currentTime)
1412+
}
1413+
1414+
binding.upcomingEventContainer.upcomingEventDismiss.setOnClickListener {
1415+
binding.upcomingEventCard.visibility = View.GONE
1416+
chatViewModel.saveHiddenUpcomingEvent(hiddenEventKey)
1417+
Snackbar.make(
1418+
binding.root,
1419+
R.string.nc_upcoming_event_dismissed,
1420+
Snackbar.LENGTH_LONG
1421+
).show()
1422+
}
1423+
}
1424+
}
1425+
1426+
is ChatViewModel.UpcomingEventUIState.Error -> {
1427+
Log.e(TAG, "Error fetching upcoming events", uiState.exception)
1428+
}
1429+
1430+
ChatViewModel.UpcomingEventUIState.None -> {
1431+
binding.upcomingEventCard.visibility = View.GONE
1432+
}
1433+
}
1434+
}
1435+
13871436
this.lifecycleScope.launch {
13881437
chatViewModel.threadRetrieveState.collect { uiState ->
13891438
when (uiState) {
@@ -2778,6 +2827,12 @@ class ChatActivity :
27782827
bundle.putString(KEY_ROOM_TOKEN, roomToken)
27792828
bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, isOneToOneConversation())
27802829

2830+
val upcomingEvent =
2831+
(chatViewModel.upcomingEventViewState.value as? ChatViewModel.UpcomingEventUIState.Success)?.event
2832+
if (upcomingEvent != null) {
2833+
bundle.putParcelable(BundleKeys.KEY_UPCOMING_EVENT, upcomingEvent)
2834+
}
2835+
27812836
val intent = Intent(this, ConversationInfoActivity::class.java)
27822837
intent.putExtras(bundle)
27832838
startActivity(intent)
@@ -3818,21 +3873,7 @@ class ChatActivity :
38183873

38193874
return when {
38203875
currentTime.isBefore(startDateTime) -> {
3821-
val isToday = startDateTime.toLocalDate().isEqual(currentTime.toLocalDate())
3822-
val isTomorrow = startDateTime.toLocalDate().isEqual(currentTime.toLocalDate().plusDays(1))
3823-
when {
3824-
isToday -> String.format(
3825-
context.resources.getString(R.string.nc_today_meeting),
3826-
startDateTime.format(DateTimeFormatter.ofPattern("HH:mm"))
3827-
)
3828-
3829-
isTomorrow -> String.format(
3830-
context.resources.getString(R.string.nc_tomorrow_meeting),
3831-
startDateTime.format(DateTimeFormatter.ofPattern("HH:mm"))
3832-
)
3833-
3834-
else -> startDateTime.format(DateTimeFormatter.ofPattern("MMM d, yyyy, HH:mm"))
3835-
}
3876+
DateUtils(context).getStringForMeetingStartDateTime(startDateTime, currentTime)
38363877
}
38373878

38383879
currentTime.isAfter(endDateTime) -> context.resources.getString(R.string.nc_meeting_ended)

app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
1616
import com.nextcloud.talk.models.json.generic.GenericOverall
1717
import com.nextcloud.talk.models.json.opengraph.Reference
1818
import com.nextcloud.talk.models.json.reminder.Reminder
19+
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEventsOverall
1920
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
2021
import io.reactivex.Observable
2122
import retrofit2.Response
@@ -70,6 +71,7 @@ interface ChatNetworkDataSource {
7071
fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int): Observable<GenericOverall>
7172
suspend fun editChatMessage(credentials: String, url: String, text: String): ChatOverallSingleMessage
7273
suspend fun getOutOfOfficeStatusForUser(credentials: String, baseUrl: String, userId: String): UserAbsenceOverall
74+
suspend fun getUpcomingEvents(credentials: String, baseUrl: String, roomToken: String): UpcomingEventsOverall
7375
suspend fun getContextForChatMessage(
7476
credentials: String,
7577
baseUrl: String,

app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
1717
import com.nextcloud.talk.models.json.generic.GenericOverall
1818
import com.nextcloud.talk.models.json.opengraph.Reference
1919
import com.nextcloud.talk.models.json.reminder.Reminder
20+
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEventsOverall
2021
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
2122
import com.nextcloud.talk.utils.ApiUtils
2223
import com.nextcloud.talk.utils.message.SendMessageUtils
@@ -194,6 +195,16 @@ class RetrofitChatNetwork(private val ncApi: NcApi, private val ncApiCoroutines:
194195
ApiUtils.getUrlForOutOfOffice(baseUrl, userId)
195196
)
196197

198+
override suspend fun getUpcomingEvents(
199+
credentials: String,
200+
baseUrl: String,
201+
roomToken: String
202+
): UpcomingEventsOverall =
203+
ncApiCoroutines.getUpcomingEvents(
204+
credentials,
205+
ApiUtils.getUrlForUpcomingEvents(baseUrl, roomToken)
206+
)
207+
197208
override suspend fun getContextForChatMessage(
198209
credentials: String,
199210
baseUrl: String,

app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import com.nextcloud.talk.models.json.generic.GenericOverall
4242
import com.nextcloud.talk.models.json.opengraph.Reference
4343
import com.nextcloud.talk.models.json.reminder.Reminder
4444
import com.nextcloud.talk.models.json.threads.ThreadInfo
45+
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEvent
4546
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceData
4647
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
4748
import com.nextcloud.talk.threadsoverview.data.ThreadsRepository
@@ -101,6 +102,7 @@ class ChatViewModel @Inject constructor(
101102
val mediaPlayerPosition = mediaPlayerManager.mediaPlayerPosition
102103
var chatRoomToken: String = ""
103104
var messageDraft: MessageDraft = MessageDraft()
105+
var hiddenUpcomingEvent: String? = null
104106
lateinit var participantPermissions: ParticipantPermissions
105107

106108
fun getChatRepository(): ChatMessageRepository = chatRepository
@@ -164,6 +166,10 @@ class ChatViewModel @Inject constructor(
164166
val outOfOfficeViewState: LiveData<OutOfOfficeUIState>
165167
get() = _outOfOfficeViewState
166168

169+
private val _upcomingEventViewState = MutableLiveData<UpcomingEventUIState>(UpcomingEventUIState.None)
170+
val upcomingEventViewState: LiveData<UpcomingEventUIState>
171+
get() = _upcomingEventViewState
172+
167173
private val _unbindRoomResult = MutableLiveData<UnbindRoomUiState>(UnbindRoomUiState.None)
168174
val unbindRoomResult: LiveData<UnbindRoomUiState>
169175
get() = _unbindRoomResult
@@ -990,6 +996,24 @@ class ChatViewModel @Inject constructor(
990996
}
991997
}
992998

999+
@Suppress("Detekt.TooGenericExceptionCaught")
1000+
fun fetchUpcomingEvent(credentials: String, baseUrl: String, roomToken: String) {
1001+
viewModelScope.launch {
1002+
updateHiddenUpcomingEvent()
1003+
try {
1004+
val response = chatNetworkDataSource.getUpcomingEvents(credentials, baseUrl, roomToken)
1005+
val firstEvent = response.ocs?.data?.events?.firstOrNull()
1006+
if (firstEvent != null) {
1007+
_upcomingEventViewState.value = UpcomingEventUIState.Success(firstEvent)
1008+
} else {
1009+
_upcomingEventViewState.value = UpcomingEventUIState.None
1010+
}
1011+
} catch (exception: Exception) {
1012+
_upcomingEventViewState.value = UpcomingEventUIState.Error(exception)
1013+
}
1014+
}
1015+
}
1016+
9931017
fun deleteTempMessage(chatMessage: ChatMessage) {
9941018
viewModelScope.launch {
9951019
chatRepository.deleteTempMessage(chatMessage)
@@ -1057,6 +1081,30 @@ class ChatViewModel @Inject constructor(
10571081
}
10581082
}
10591083

1084+
suspend fun updateHiddenUpcomingEvent() {
1085+
val model = conversationRepository.getLocallyStoredConversation(
1086+
currentUser,
1087+
chatRoomToken
1088+
)
1089+
model?.hiddenUpcomingEvent?.let {
1090+
hiddenUpcomingEvent = it
1091+
}
1092+
}
1093+
1094+
fun saveHiddenUpcomingEvent(value: String) {
1095+
hiddenUpcomingEvent = value
1096+
viewModelScope.launch {
1097+
val model = conversationRepository.getLocallyStoredConversation(
1098+
currentUser,
1099+
chatRoomToken
1100+
)
1101+
model?.let {
1102+
it.hiddenUpcomingEvent = value
1103+
conversationRepository.updateConversation(it)
1104+
}
1105+
}
1106+
}
1107+
10601108
fun pinMessage(credentials: String, url: String, pinUntil: Int = 0) {
10611109
viewModelScope.launch {
10621110
chatRepository.pinMessage(credentials, url, pinUntil).collect {
@@ -1118,4 +1166,10 @@ class ChatViewModel @Inject constructor(
11181166
data class Success(val thread: ThreadInfo?) : ThreadRetrieveUiState()
11191167
data class Error(val exception: Exception) : ThreadRetrieveUiState()
11201168
}
1169+
1170+
sealed class UpcomingEventUIState {
1171+
data object None : UpcomingEventUIState()
1172+
data class Success(val event: UpcomingEvent) : UpcomingEventUIState()
1173+
data class Error(val exception: Exception) : UpcomingEventUIState()
1174+
}
11211175
}

app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
6161
import com.nextcloud.talk.databinding.DialogBanParticipantBinding
6262
import com.nextcloud.talk.events.EventStatus
6363
import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider
64+
import com.nextcloud.talk.extensions.getParcelableExtraProvider
6465
import com.nextcloud.talk.extensions.loadConversationAvatar
6566
import com.nextcloud.talk.extensions.loadNoteToSelfAvatar
6667
import com.nextcloud.talk.extensions.loadSystemAvatar
@@ -73,6 +74,7 @@ import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelCo
7374
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
7475
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
7576
import com.nextcloud.talk.models.json.conversations.ConversationEnums
77+
import com.nextcloud.talk.models.json.upcomingEvents.UpcomingEvent
7678
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
7779
import com.nextcloud.talk.models.json.generic.GenericOverall
7880
import com.nextcloud.talk.models.json.participants.Participant
@@ -105,6 +107,7 @@ import kotlinx.coroutines.launch
105107
import org.greenrobot.eventbus.Subscribe
106108
import org.greenrobot.eventbus.ThreadMode
107109
import java.time.Instant
110+
import java.time.ZoneId
108111
import java.time.ZoneOffset
109112
import java.time.ZonedDateTime
110113
import java.time.format.DateTimeFormatter
@@ -200,6 +203,19 @@ class ConversationInfoActivity :
200203

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

206+
val upcomingEvent = intent.getParcelableExtraProvider<UpcomingEvent>(BundleKeys.KEY_UPCOMING_EVENT)
207+
if (upcomingEvent != null && (upcomingEvent.summary != null || upcomingEvent.start != null)) {
208+
binding.upcomingEventCard.visibility = VISIBLE
209+
viewThemeUtils.material.themeCardView(binding.upcomingEventCard)
210+
binding.upcomingEventContainer.upcomingEventSummary.text = upcomingEvent.summary
211+
upcomingEvent.start?.let { start ->
212+
val startDateTime = Instant.ofEpochSecond(start).atZone(ZoneId.systemDefault())
213+
val currentTime = ZonedDateTime.now(ZoneId.systemDefault())
214+
binding.upcomingEventContainer.upcomingEventTime.text =
215+
DateUtils(this).getStringForMeetingStartDateTime(startDateTime, currentTime)
216+
}
217+
}
218+
203219
viewModel = ViewModelProvider(this, viewModelFactory)[ConversationInfoViewModel::class.java]
204220

205221
lifecycleScope.launch {

app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class OfflineFirstConversationsRepository @Inject constructor(
9393

9494
override fun onNext(model: ConversationModel) {
9595
runBlocking {
96+
val existingEntity = dao.getConversationForUser(user.id!!, model.token).first()
97+
model.hiddenUpcomingEvent = existingEntity?.hiddenUpcomingEvent
9698
_conversationFlow.emit(model)
9799
val entityList = listOf(model.asEntity())
98100
dao.upsertConversations(user.id!!, entityList)

app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface ConversationsDao {
3232
if (existingItem != null) {
3333
val mergedItem = serverItem.copy()
3434
mergedItem.messageDraft = existingItem.messageDraft
35+
mergedItem.hiddenUpcomingEvent = existingItem.hiddenUpcomingEvent
3536
updateConversation(mergedItem)
3637
} else {
3738
insertConversation(serverItem)

app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ fun ConversationModel.asEntity() =
6666
hasImportant = hasImportant,
6767
messageDraft = messageDraft,
6868
hiddenPinnedId = hiddenPinnedId,
69-
lastPinnedId = lastPinnedId
69+
lastPinnedId = lastPinnedId,
70+
hiddenUpcomingEvent = hiddenUpcomingEvent
7071
)
7172

7273
fun ConversationEntity.asModel() =
@@ -123,7 +124,8 @@ fun ConversationEntity.asModel() =
123124
hasImportant = hasImportant,
124125
messageDraft = messageDraft,
125126
hiddenPinnedId = hiddenPinnedId,
126-
lastPinnedId = lastPinnedId
127+
lastPinnedId = lastPinnedId,
128+
hiddenUpcomingEvent = hiddenUpcomingEvent
127129
)
128130

129131
fun Conversation.asEntity(accountId: Long) =

0 commit comments

Comments
 (0)